JavaScript元編程

從 ECMAScript 2015 開始,JavaScript 獲得了 ProxyReflect 對象的支持,允許攔截並定義基本語言操作的自定義行爲(例如,屬性查找,賦值,枚舉,函數調用等)。藉助這兩個對象,可以在 JavaScript 元級別進行編程。

元編程(Metaprogramming)是一種編程技術,其中計算機程序能夠將其他程序視爲其數據。這意味着一個程序可以被設計爲讀取、生成、分析或轉換其他程序,甚至在運行時修改自身。在某些情況下,這允許程序員最大限度地減少表達解決方案的代碼行數,從而減少開發時間。它還使程序具有更大的靈活性,可以有效地處理新情況而無需重新編譯。

Proxy

Proxy 用於修改某些操作的默認行爲,等同於在語言層面做出修改。Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

new Proxy(target, handler)
  • target:被代理的對象,Proxy 會對 target 對象進行包裝。它可以是任何類型的對象,包括內置的數組,函數甚至是另一個代理對象。
  • handler:被代理對象上的自定義行爲,它是一個對象,它的屬性提供了某些操作發生時所對應的處理函數。

句柄和陷阱

  1. handler.apply() 方法是 [[Call]] 對象內部方法的陷阱,供函數調用等操作使用。該陷阱可以攔截以下操作:

    • 函數調用: proxy(...args)
    • Function.prototype.apply()Function.prototype.call()
    • Reflect.apply()
    • 調用 [[Call]] 內部方法的任何其他操作
  2. handler.construct() 方法是 [[Construct]] 對象內部方法的陷阱,由 new 運算符等操作使用。爲了使 new 操作在生成的 Proxy 對象上有效,用於初始化代理的目標本身必須是有效的構造函數。construct 方法必須返回一個對象。該陷阱可以攔截以下操作:

    • new 運算符: new myFunction(...args)
    • Reflect.construct()
    • 調用 [[Construct]] 內部方法的任何其他操作。
  3. handler.defineProperty() 方法是 [[DefineOwnProperty]] 對象內部方法的陷阱,由 Object.defineProperty() 等操作使用。該陷阱可以攔截以下操作:

    • Object.defineProperty()Object.defineProperties()
    • Reflect.defineProperty()
    • 調用 [[DefineOwnProperty]] 內部方法的任何其他操作。
  4. handler.deleteProperty() 方法是 [[Delete]] 對象內部方法的陷阱,由 delete 運算符等操作使用。該陷阱可以攔截以下操作:

    • delete 運算符: delete proxy[prop]delete proxy.prop
    • Reflect.deleteProperty()
    • 調用 [[Delete]] 內部方法的任何其他操作。
  5. handler.get() 方法是 [[Get]] 對象內部方法的陷阱,由屬性訪問器等操作使用。該陷阱可以攔截以下操作:

    • 屬性訪問: proxy[prop]proxy.prop
    • Reflect.get()
    • 調用 [[Get]] 內部方法的任何其他操作。
  6. handler.getOwnPropertyDescriptor() 方法是 [[GetOwnProperty]] 對象內部方法的陷阱,由 Object.getOwnPropertyDescriptor() 等操作使用。該陷阱可以攔截以下操作:

    • Object.getOwnPropertyDescriptor()
    • Reflect.getOwnPropertyDescriptor()
    • 調用 [[GetOwnProperty]] 內部方法的任何其他操作。
  7. handler.getPrototypeOf() 方法是 [[GetPrototypeOf]] 對象內部方法的陷阱,由 Object.getPrototypeOf() 等操作使用。該陷阱可以攔截以下操作:

    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • __proto__
    • Object.prototype.isPrototypeOf()
    • instanceof
    • 調用 [[GetPrototypeOf]] 內部方法的任何其他操作。
  8. handler.has() 方法是 [[HasProperty]] 對象內部方法的陷阱,由 in 運算符等操作使用。該陷阱可以攔截以下操作:

    • in 運算符: prop in proxy
    • with 檢查: with(proxy) { (prop); }
    • Reflect.has()
    • 調用 [[HasProperty]] 內部方法的任何其他操作。
  9. handler.isExtensible() 方法是 [[IsExtensible]] 對象內部方法的陷阱,由 Object.isExtensible() 等操作使用。該陷阱可以攔截以下操作:

    • Object.isExtensible()
    • Reflect.isExtensible()
    • 調用 [[IsExtensible]] 內部方法的任何其他操作。
  10. handler.ownKeys() 方法是 [[OwnPropertyKeys]] 對象內部方法的陷阱,由 Object.keys()Reflect.ownKeys() 等操作使用。該陷阱可以攔截以下操作:

    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • Reflect.ownKeys()
    • 調用 [[OwnPropertyKeys]] 內部方法的任何其他操作。
  11. handler.preventExtensions() 方法是 [[PreventExtensions]] 對象內部方法的陷阱,由 Object.preventExtensions() 等操作使用。該陷阱可以攔截以下操作:

    • Object.preventExtensions()
    • Reflect.preventExtensions()
    • Object.seal()
    • Object.freeze()
    • 調用 [[PreventExtensions]] 內部方法的任何其他操作。
  12. handler.set() 方法是 [[Set]] 對象內部方法的陷阱,該方法由諸如使用屬性訪問器設置屬性值之類的操作使用。該陷阱可以攔截以下操作:

    • 屬性賦值: proxy[prop] = valueproxy.prop = value
    • Reflect.set()
    • 調用 [[Set]] 內部方法的任何其他操作。
  13. handler.setPrototypeOf() 方法是 [[SetPrototypeOf]] 對象內部方法的陷阱,由 Object.setPrototypeOf() 等操作使用。該陷阱可以攔截以下操作:

    • Object.setPrototypeOf()
    • Reflect.setPrototypeOf()
    • 調用 [[SetPrototypeOf]] 內部方法的任何其他操作。

撤銷 Proxy

Proxy.revocable() 方法被用來創建可撤銷的 Proxy 對象。這意味着 proxy 可以通過 revoke 函數來撤銷,並且關閉代理。此後,代理上的任意的操作都會導致 TypeError

Proxy.revocable(target, handler)

Reflect

Reflect 是一個內置對象,它提供了可攔截 JavaScript 操作的方法。該方法和 Proxy handler 類似,但 Reflect 方法並不是一個函數對象。

  1. Reflect.apply(target, thisArgument, argumentsList) 對一個函數進行調用操作,同時可以傳入一個數組作爲調用參數。和 Function.prototype.apply() 功能類似。
  2. Reflect.construct(target, argumentsList[, newTarget]) 對構造函數進行 new 操作,相當於執行 new target(...args)
  3. Reflect.defineProperty(target, propertyKey, attributes)Object.defineProperty() 類似。如果設置成功就會返回 true
  4. Reflect.deleteProperty(target, propertyKey) 作爲函數的 delete 操作符,相當於執行 delete target[name]
  5. Reflect.get(target, propertyKey[, receiver]) 獲取對象身上某個屬性的值,類似於 target[name]
  6. Reflect.getOwnPropertyDescriptor(target, propertyKey) 類似於 Object.getOwnPropertyDescriptor()。如果對象中存在該屬性,則返回對應的屬性描述符,否則返回 undefined
  7. Reflect.getPrototypeOf(target) 類似於 Object.getPrototypeOf()
  8. Reflect.has(target, propertyKey) 判斷一個對象是否存在某個屬性,和 in 運算符 的功能完全相同。
  9. Reflect.isExtensible(target) 類似於 Object.isExtensible()
  10. Reflect.ownKeys(target) 返回一個包含所有自身屬性(不包含繼承屬性)的數組。(類似於 Object.keys(), 但不會受 enumerable 影響).
  11. Reflect.preventExtensions(target) 類似於 Object.preventExtensions()。返回一個 Boolean
  12. Reflect.set(target, propertyKey, value[, receiver]) 將值分配給屬性的函數。返回一個 Boolean,如果更新成功,則返回 true
  13. Reflect.setPrototypeOf(target, prototype) 設置對象原型的函數。返回一個 Boolean,如果更新成功,則返回 true

Reflect 有助於將默認操作從處理程序轉發到目標。

  • Object 對象的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到 Reflect 對象上。現階段,某些方法同時在 ObjectReflect 對象上部署,未來的新方法將只部署在 Reflect 對象上。也就是說,從 Reflect 對象上可以拿到語言內部的方法。
  • 修改某些 Object 方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc) 在無法定義屬性時,會拋出一個錯誤,而 Reflect.defineProperty(obj, name, desc) 則會返回 false
  • Object 操作都變成函數行爲。某些 Object 操作是命令式,比如 name in objdelete obj[name],而 Reflect.has(obj, name)Reflect.deleteProperty(obj, name) 讓它們變成了函數行爲。
  • Reflect 對象的方法與 Proxy 對象的方法一一對應,只要是 Proxy 對象的方法,就能在 Reflect 對象上找到對應的方法。這就讓 Proxy 對象可以方便地調用對應的 Reflect 方法,完成默認行爲,作爲修改行爲的基礎。也就是說,不管 Proxy 怎麼修改默認行爲,總可以在 Reflect 上獲取默認行爲。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章