Proxy のトラップで言語の基本的な操作をインターセプトし、Reflect でデフォルトの動作をきれいに委譲します。
このレッスンを Kodokon で開くProxy はターゲットオブジェクトをラップし、13 個のトラップを通じて言語の内部操作をインターセプトします。get、set、has(in 演算子)、deleteProperty、ownKeys、apply、construct などです。Reflect オブジェクトは、まさに同じ操作を関数として、トラップと同じシグネチャで公開します。これは Vue 3 のリアクティビティ、動的なモック、そして ORM 風の API の基盤です。
const target = { name: "Ada" };
const traced = new Proxy(target, {
get(obj, prop, receiver) {
console.log("get " + String(prop));
return Reflect.get(obj, prop, receiver);
}
});
traced.name; // get nameなぜ obj[prop] ではなく Reflect.get(obj, prop, receiver) なのでしょうか?それはレシーバ(receiver)のためです。プロパティがアクセサ(ゲッター)である場合、Reflect.get は this をレシーバ(プロキシ、あるいはそれを継承したオブジェクト)に向けて呼び出しますが、obj[prop] は this を生のターゲットに設定してしまいます。レシーバがなければ、this を読むゲッターは、オブジェクトがプロキシを継承した途端に壊れてしまいます。
const base = {
get label() {
return this.name;
}
};
const proxy = new Proxy(base, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
const child = Object.create(proxy);
child.name = "Ada";
console.log(child.label); // "Ada" thanks to the receiver本格的なユースケースはこうです。書き込み時のバリデーション、その場で計算される仮想プロパティ、ロギング、アクセス制御(has と ownKeys を通じて _ で始まるキーを隠す)、あるいはデフォルトの動作を持つオブジェクト。なお、Reflect.set は strict モードで例外を投げる代わりに真偽値を返します。あなたのトラップはその戻り値を伝播させなければなりません。
function validated(target) {
return new Proxy(target, {
set(obj, prop, value, receiver) {
if (prop === "age" && !Number.isInteger(value)) {
throw new TypeError("age must be an integer");
}
return Reflect.set(obj, prop, value, receiver);
}
});
}
const user = validated({ age: 30 });
user.age = 31; // ok
// user.age = "old"; // TypeErrorget トラップの中で、なぜ target[prop] より Reflect.get(target, prop, receiver) を選ぶのですか?this(レシーバ)で実行されるようにするためtarget[prop] はトラップを再び発火させ、無限再帰を引き起こすから"id" in obj をインターセプトするトラップはどれですか?get トラップが 42 を返します。何が起こりますか?