維基百科:javascript
元編程
(meta programming)是一種編程技術,編寫出來的計算機程序可以將其餘程序做爲數據來處理。java意味着能夠編寫出這樣的程序:它可以
讀取、生成、分析或者轉換
其它程序,甚至在運行時修改程序自身
(反射)。es6
元編程中的 元
的概念能夠理解爲 程序 自己,元編程關注如下的一點或幾點:編程
一、運行時修改語言結構,這種現象被稱爲 反射編程
或 反射
;數組
默認的語言行爲
而使其餘代碼受影響;代碼可以自我檢查、訪問內部屬性,得到代碼的底層信息!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
顧名思義,代碼能夠修改自身屬性或者其餘底層信息!函數
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("元編程"); }
在開發過程當中自我修改應該要盡力避免,能夠想象:正在使用一個數據的同時又在修改這個數據,後容易形成不可預期的錯誤!
代碼修改默認的語言行爲
而使其餘代碼受影響,最明顯的體現爲改變其它對象的語義!
在元編程中,調解的概念相似於包裝、捕獲、攔截。
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
屬性。
MDN:
從ECMAScript 2015 開始,JavaScript 得到了Proxy
和Reflect
對象的支持,容許你攔截並定義基本語言操做的自定義行爲(例如,屬性查找,賦值,枚舉,函數調用等)。藉助這兩個對象,你能夠在 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
是一個內置對象,它提供了可攔截 JavaScript 操做的方法。
該方法和Proxy的handler
相似,但 Reflect
方法並非一個函數對象,所以它是不可構造的。
Reflect
的全部屬性和方法都是靜態的(就像Math
對象);
以 Reflect.has()
爲例,與 in
運算符對比,檢測一個對象是否存在特定屬性:
Reflect.has(Object, "assign"); // true "assign" in Object; // true
在 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。
js中使用元編程技術生成代碼最多見的函數 eval()
:函數會將傳入的字符串當作 JavaScript 代碼進行執行。
let str = "function sayHello(){console.log('hello')}"; eval(str); sayHello(); // 輸出 hello
元編程是當你將程序的邏輯轉向關注它自身(或者它的運行時環境)時進行的編程,要麼爲了調查它本身的結構,要麼爲了修改它。
元編程的主要價值是擴展語言的普通機制來提供額外的能力
。