從 ECMAScript 2015 開始,JavaScript 獲得了 Proxy
和 Reflect
對象的支持,允許攔截並定義基本語言操作的自定義行爲(例如,屬性查找,賦值,枚舉,函數調用等)。藉助這兩個對象,可以在 JavaScript 元級別進行編程。
元編程(Metaprogramming)是一種編程技術,其中計算機程序能夠將其他程序視爲其數據。這意味着一個程序可以被設計爲讀取、生成、分析或轉換其他程序,甚至在運行時修改自身。在某些情況下,這允許程序員最大限度地減少表達解決方案的代碼行數,從而減少開發時間。它還使程序具有更大的靈活性,可以有效地處理新情況而無需重新編譯。
Proxy
Proxy
用於修改某些操作的默認行爲,等同於在語言層面做出修改。Proxy
可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
new Proxy(target, handler)
target
:被代理的對象,Proxy
會對target
對象進行包裝。它可以是任何類型的對象,包括內置的數組,函數甚至是另一個代理對象。handler
:被代理對象上的自定義行爲,它是一個對象,它的屬性提供了某些操作發生時所對應的處理函數。
句柄和陷阱
-
handler.apply()
方法是[[Call]]
對象內部方法的陷阱,供函數調用等操作使用。該陷阱可以攔截以下操作:- 函數調用:
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
- 調用
[[Call]]
內部方法的任何其他操作
- 函數調用:
-
handler.construct()
方法是[[Construct]]
對象內部方法的陷阱,由new
運算符等操作使用。爲了使new
操作在生成的Proxy
對象上有效,用於初始化代理的目標本身必須是有效的構造函數。construct
方法必須返回一個對象。該陷阱可以攔截以下操作:new
運算符:new myFunction(...args)
Reflect.construct()
- 調用
[[Construct]]
內部方法的任何其他操作。
-
handler.defineProperty()
方法是[[DefineOwnProperty]]
對象內部方法的陷阱,由Object.defineProperty()
等操作使用。該陷阱可以攔截以下操作:Object.defineProperty()
,Object.defineProperties()
Reflect.defineProperty()
- 調用
[[DefineOwnProperty]]
內部方法的任何其他操作。
-
handler.deleteProperty()
方法是[[Delete]]
對象內部方法的陷阱,由delete
運算符等操作使用。該陷阱可以攔截以下操作:delete
運算符:delete proxy[prop]
和delete proxy.prop
Reflect.deleteProperty()
- 調用
[[Delete]]
內部方法的任何其他操作。
-
handler.get()
方法是[[Get]]
對象內部方法的陷阱,由屬性訪問器等操作使用。該陷阱可以攔截以下操作:- 屬性訪問:
proxy[prop]
和proxy.prop
Reflect.get()
- 調用
[[Get]]
內部方法的任何其他操作。
- 屬性訪問:
-
handler.getOwnPropertyDescriptor()
方法是[[GetOwnProperty]]
對象內部方法的陷阱,由Object.getOwnPropertyDescriptor()
等操作使用。該陷阱可以攔截以下操作:Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
- 調用
[[GetOwnProperty]]
內部方法的任何其他操作。
-
handler.getPrototypeOf()
方法是[[GetPrototypeOf]]
對象內部方法的陷阱,由Object.getPrototypeOf()
等操作使用。該陷阱可以攔截以下操作:Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
- 調用
[[GetPrototypeOf]]
內部方法的任何其他操作。
-
handler.has()
方法是[[HasProperty]]
對象內部方法的陷阱,由in
運算符等操作使用。該陷阱可以攔截以下操作:in
運算符:prop in proxy
with
檢查:with(proxy) { (prop); }
Reflect.has()
- 調用
[[HasProperty]]
內部方法的任何其他操作。
-
handler.isExtensible()
方法是[[IsExtensible]]
對象內部方法的陷阱,由Object.isExtensible()
等操作使用。該陷阱可以攔截以下操作:Object.isExtensible()
Reflect.isExtensible()
- 調用
[[IsExtensible]]
內部方法的任何其他操作。
-
handler.ownKeys()
方法是[[OwnPropertyKeys]]
對象內部方法的陷阱,由Object.keys()
、Reflect.ownKeys()
等操作使用。該陷阱可以攔截以下操作:Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
- 調用
[[OwnPropertyKeys]]
內部方法的任何其他操作。
-
handler.preventExtensions()
方法是[[PreventExtensions]]
對象內部方法的陷阱,由Object.preventExtensions()
等操作使用。該陷阱可以攔截以下操作:Object.preventExtensions()
Reflect.preventExtensions()
Object.seal()
Object.freeze()
- 調用
[[PreventExtensions]]
內部方法的任何其他操作。
-
handler.set()
方法是[[Set]]
對象內部方法的陷阱,該方法由諸如使用屬性訪問器設置屬性值之類的操作使用。該陷阱可以攔截以下操作:- 屬性賦值:
proxy[prop] = value
和proxy.prop = value
Reflect.set()
- 調用
[[Set]]
內部方法的任何其他操作。
- 屬性賦值:
-
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
方法並不是一個函數對象。
Reflect.apply(target, thisArgument, argumentsList)
對一個函數進行調用操作,同時可以傳入一個數組作爲調用參數。和Function.prototype.apply()
功能類似。Reflect.construct(target, argumentsList[, newTarget])
對構造函數進行new
操作,相當於執行new target(...args)
。Reflect.defineProperty(target, propertyKey, attributes)
和Object.defineProperty()
類似。如果設置成功就會返回true
。Reflect.deleteProperty(target, propertyKey)
作爲函數的delete
操作符,相當於執行delete target[name]
。Reflect.get(target, propertyKey[, receiver])
獲取對象身上某個屬性的值,類似於target[name]
。Reflect.getOwnPropertyDescriptor(target, propertyKey)
類似於Object.getOwnPropertyDescriptor()
。如果對象中存在該屬性,則返回對應的屬性描述符,否則返回undefined
。Reflect.getPrototypeOf(target)
類似於Object.getPrototypeOf()
。Reflect.has(target, propertyKey)
判斷一個對象是否存在某個屬性,和in
運算符 的功能完全相同。Reflect.isExtensible(target)
類似於Object.isExtensible()
。Reflect.ownKeys(target)
返回一個包含所有自身屬性(不包含繼承屬性)的數組。(類似於Object.keys()
, 但不會受enumerable
影響).Reflect.preventExtensions(target)
類似於Object.preventExtensions()
。返回一個Boolean
。Reflect.set(target, propertyKey, value[, receiver])
將值分配給屬性的函數。返回一個Boolean
,如果更新成功,則返回true
。Reflect.setPrototypeOf(target, prototype)
設置對象原型的函數。返回一個Boolean
,如果更新成功,則返回true
。
Reflect
有助於將默認操作從處理程序轉發到目標。
- 將
Object
對象的一些明顯屬於語言內部的方法(比如Object.defineProperty
),放到Reflect
對象上。現階段,某些方法同時在Object
和Reflect
對象上部署,未來的新方法將只部署在Reflect
對象上。也就是說,從Reflect
對象上可以拿到語言內部的方法。 - 修改某些
Object
方法的返回結果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)
在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)
則會返回false
。 - 讓
Object
操作都變成函數行爲。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
讓它們變成了函數行爲。 Reflect
對象的方法與Proxy
對象的方法一一對應,只要是Proxy
對象的方法,就能在Reflect
對象上找到對應的方法。這就讓Proxy
對象可以方便地調用對應的Reflect
方法,完成默認行爲,作爲修改行爲的基礎。也就是說,不管Proxy
怎麼修改默認行爲,總可以在Reflect
上獲取默認行爲。