JS中無處不在的元編程

什麼是元編程?

維基百科:javascript

元編程 (meta programming)是一種編程技術,編寫出來的計算機程序可以將其餘程序做爲數據來處理。java

意味着能夠編寫出這樣的程序:它可以讀取、生成、分析或者轉換其它程序,甚至在運行時修改程序自身(反射)。es6

元編程中的 的概念能夠理解爲 程序 自己,元編程關注如下的一點或幾點:編程

  • 一、運行時修改語言結構,這種現象被稱爲 反射編程反射數組

    • 自省:代碼檢視本身;
    • 自我修改:代碼修改本身;
    • 調解:代碼修改默認的語言行爲而使其餘代碼受影響;
  • 二、生成代碼;

1、自省

代碼可以自我檢查、訪問內部屬性,得到代碼的底層信息!app

// 訪問對象自身屬性
var users = {
    'Tom': 32,
    'Bill': 50,
    'Sam': 65
};

Object.keys(users).forEach(name => {
    const age = users[name];
    console.log(`User ${name} is ${age} years old!`);
});

// 輸出結果
User Tom is 32 years old!
User Bill is 50 years old!
User Sam is 65 years old!

自省在平時的業務開發中很常見,這也是元編程技術的一種使用!ide

2、自我修改

顧名思義,代碼能夠修改自身屬性或者其餘底層信息!函數

let a = 1;
if (a == 1 && a == 2 && a == 3) {
    console.log("元編程");
}

上述代碼在正常狀況下是不管如何也沒法知足條件輸出,ui

由於一個值不可能同時知足等於一、二、3;可是,利用元編程就能夠實現:.net

// 修改自身
let a = {
    [Symbol.toPrimitive]: ((i) => () => ++i)(0);
}

if (a == 1 && a == 2 && a == 3) {
    console.log("元編程");
}

// 輸出 '元編程'

Symbol.toPrimitive 是一個內置的 Symbol 值,它是做爲對象的函數值屬性存在的;

在對象轉換爲原始值的時候會被調用,初始值爲1,調用一次+1,就能夠知足a == 1 && a == 2 && a == 3

上述函數變形爲:

let a = {
    [Symbol.toPrimitive]: (function (i){
        return function (){
            return ++i
        }
    })(0)
}

if (a == 1 && a == 2 && a == 3) {
    console.log("元編程");
}

在開發過程當中自我修改應該要盡力避免,能夠想象:正在使用一個數據的同時又在修改這個數據,後容易形成不可預期的錯誤!

3、調解

代碼修改默認的語言行爲而使其餘代碼受影響,最明顯的體現爲改變其它對象的語義!

在元編程中,調解的概念相似於包裝、捕獲、攔截。

Object.defineProperty()就是典型的 調解 的運用:

var sun = {};
Object.defineProperty(sun, 'rises', {
    value: true,
    configurable: false,
    writable: false,
    enumerable: false
});

console.log('sun rises', sun.rises);
sun.rises = false;
console.log('sun rises', sun.rises);

// 輸出
sun rises true
sun rises true

上面例子中,新建立了一個普通對象 sun,以後經過Object.defineProperty 改變了它的語義:爲其定義了一個不可寫的 rises 屬性。

4、Reflect & Proxy

MDN:

從ECMAScript 2015 開始,JavaScript 得到了 ProxyReflect 對象的支持,容許你攔截並定義基本語言操做的自定義行爲(例如,屬性查找,賦值,枚舉,函數調用等)。藉助這兩個對象,你能夠在 JavaScript 元級別進行編程。
// Proxy的handler 和 Reflect 對象 13 個方法

.apply()                                                        // 對一個函數進行調用操做, 和 Function.prototype.apply() 功能相似
.construct()                                                // 對構造函數進行 new 操做,至關於執行 new target(...args)
.get()                                                            // 獲取對象身上某個屬性的值,相似於 target[name]。
.has()                                                            // 判斷一個對象是否存在某個屬性,和 in 運算符 的功能徹底相同
.ownKeys()                                                    // 返回一個包含全部自身屬性(不包含繼承屬性)的數組,相似於 Object.keys()
.set()                                                            // 將值分配給屬性的函數。返回一個Boolean,若是更新成功,則返回true
.setPrototypeOf()                                        // 設置對象原型的函數. 返回一個 Boolean, 若是更新成功,則返回true。
.defineProperty()                                        // Object.defineProperty() 相似
.deleteProperty()                                        // 做爲函數的delete操做符,至關於執行 delete target[name]。
.getOwnPropertyDescriptor()                    //對象中存在該屬性,則返回對應的屬性描述符,相似於 Object.getOwnPropertyDescriptor()
.getPrototypeOf()                                        // 相似於 Object.getPrototypeOf()。
.isExtensible()                                            // 相似於 Object.isExtensible()
.preventExtensions()                                // 相似於 Object.preventExtensions()。返回一個Boolean。

一、Reflect

Reflect 是一個內置對象,它提供了可攔截 JavaScript 操做的方法。

該方法和Proxy的handler相似,但 Reflect 方法並非一個函數對象,所以它是不可構造的。

Reflect的全部屬性和方法都是靜態的(就像Math對象);

Reflect.has() 爲例,與 in 運算符對比,檢測一個對象是否存在特定屬性:

Reflect.has(Object, "assign");     // true
"assign" in Object;                         // true

二、Proxy

在 ECMAScript 6 中引入的 Proxy 對象能夠攔截某些操做並實現自定義行爲。

例如獲取一個對象上的屬性:

let handler = {
  get: function(target, name){
    return name in target ? target[name] : 42;
}};

let p = new Proxy({}, handler);
p.a = 1;

console.log(p.a, p.b); // 1, 42

Proxy 對象定義了一個目標(這裏是一個空對象)和一個實現了 get 劫持的 handler 對象。

代理的對象在獲取未定義的屬性時不會返回 undefined,而是返回 42。

5、生成代碼

js中使用元編程技術生成代碼最多見的函數 eval():函數會將傳入的字符串當作 JavaScript 代碼進行執行。

let str = "function sayHello(){console.log('hello')}";
eval(str);
sayHello();

// 輸出
hello

6、總結

元編程是當你將程序的邏輯轉向關注它自身(或者它的運行時環境)時進行的編程,要麼爲了調查它本身的結構,要麼爲了修改它。

元編程的主要價值是擴展語言的普通機制來提供額外的能力

參考

相關文章
相關標籤/搜索