超程式設計
Proxy 和 Reflect 物件使得你可以攔截併為基本語言操作定義自定義行為(例如,屬性查詢、賦值、列舉、函式呼叫等)。在這兩個物件的幫助下,你可以在 JavaScript 的元級別進行程式設計。
代理
Proxy 物件允許你攔截某些操作並實現自定義行為。
例如,獲取物件的屬性:
const handler = {
get(target, name) {
return name in target ? target[name] : 42;
},
};
const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Proxy 物件定義了一個 target(此處為一個空物件)和一個 handler 物件,其中實現了一個 get 陷阱。在這裡,一個被代理的物件在獲取未定義的屬性時不會返回 undefined,而是會返回數字 42。
更多示例可在 Proxy 參考頁面上找到。
術語
在討論代理的功能時,會使用以下術語。
- handler
-
包含陷阱的佔位符物件。
- traps
-
提供屬性訪問的方法。(這類似於作業系統中陷阱的概念。)
- 目標
-
代理所虛擬化的物件。它通常用作代理的儲存後端。關於物件不可擴充套件性或不可配置屬性的不變數(保持不變的語義)會根據目標進行驗證。
- invariants
-
在實現自定義操作時保持不變的語義被稱為不變數。如果違反了處理器的不變數,將會丟擲
TypeError。
處理器與陷阱
下表總結了 Proxy 物件可用的陷阱。詳細解釋和示例請參見參考頁面。
可撤銷的 Proxy
Proxy.revocable() 方法用於建立一個可撤銷的 Proxy 物件。這意味著代理可以透過函式 revoke 被撤銷,並關閉該代理。
之後,對代理的任何操作都會導致 TypeError。
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return `[[${name}]]`;
},
},
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", typeof doesn't trigger any trap
反射
Reflect 是一個內建物件,它提供了可攔截 JavaScript 操作的方法。這些方法與代理處理器的方法相同。
Reflect 不是一個函式物件。
Reflect 有助於將預設操作從處理器轉發到 target。
例如,使用 Reflect.has(),你可以將 in 運算子作為一個函式來使用。
Reflect.has(Object, "assign"); // true
一個更好的 apply() 函式
在 Reflect 出現之前,你通常使用 Function.prototype.apply() 方法來呼叫一個函式,並指定一個 this 值和以陣列(或類陣列物件)形式提供的 arguments。
Function.prototype.apply.call(Math.floor, undefined, [1.75]);
使用 Reflect.apply,這變得不那麼冗長,也更容易理解。
Reflect.apply(Math.floor, undefined, [1.75]);
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4
Reflect.apply("".charAt, "ponies", [3]);
// "i"
檢查屬性定義是否成功
使用 Object.defineProperty 時,如果成功,它會返回一個物件,否則會丟擲一個 TypeError,因此你需要使用 try...catch 塊來捕獲在定義屬性時發生的任何錯誤。而 Reflect.defineProperty() 返回一個布林型別的成功狀態,所以你只需使用一個 if...else 塊即可。
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}