超程式設計

ProxyReflect 物件使得你可以攔截併為基本語言操作定義自定義行為(例如,屬性查詢、賦值、列舉、函式呼叫等)。在這兩個物件的幫助下,你可以在 JavaScript 的元級別進行程式設計。

代理

Proxy 物件允許你攔截某些操作並實現自定義行為。

例如,獲取物件的屬性:

js
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 物件可用的陷阱。詳細解釋和示例請參見參考頁面

處理器/陷阱 攔截的操作
handler.getPrototypeOf() Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
handler.setPrototypeOf() Object.setPrototypeOf()
Reflect.setPrototypeOf()
handler.isExtensible() Object.isExtensible()
Reflect.isExtensible()
handler.preventExtensions() Object.preventExtensions()
Reflect.preventExtensions()
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
handler.has()
屬性查詢
foo in proxy
繼承屬性查詢
foo in Object.create(proxy)
Reflect.has()
handler.get()
屬性訪問
proxy[foo]
proxy.bar
繼承屬性訪問
Object.create(proxy)[foo]
Reflect.get()
handler.set()
屬性賦值
proxy[foo] = bar
proxy.foo = bar
繼承屬性賦值
Object.create(proxy)[foo] = bar
Reflect.set()
handler.deleteProperty()
屬性刪除
delete proxy[foo]
delete proxy.foo
Reflect.deleteProperty()
handler.ownKeys() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
handler.apply() proxy(..args)
Function.prototype.apply()Function.prototype.call()
Reflect.apply()
handler.construct() new proxy(...args)
Reflect.construct()

可撤銷的 Proxy

Proxy.revocable() 方法用於建立一個可撤銷的 Proxy 物件。這意味著代理可以透過函式 revoke 被撤銷,並關閉該代理。

之後,對代理的任何操作都會導致 TypeError

js
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 運算子作為一個函式來使用。

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

一個更好的 apply() 函式

Reflect 出現之前,你通常使用 Function.prototype.apply() 方法來呼叫一個函式,並指定一個 this 值和以陣列(或類陣列物件)形式提供的 arguments

js
Function.prototype.apply.call(Math.floor, undefined, [1.75]);

使用 Reflect.apply,這變得不那麼冗長,也更容易理解。

js
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 塊即可。

js
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}