ES6 の新しいプロキシ (Proxy) とリフレクション (Reflect) は、開発者に追加の動作をインターセプトして基本操作に埋め込む機能を提供します。具体的には、関連するプロキシ オブジェクトをターゲット オブジェクトに対して定義でき、このプロキシ オブジェクトを抽象オブジェクトとして使用できます。ターゲット オブジェクトが使用されます。つまり、ターゲット オブジェクトに対してさまざまな操作を実行する前に、これらの操作をプロキシ オブジェクトで制御できます。
⚠️: プロキシは新しい基本的な言語機能であるため、多くの翻訳者はプロキシの動作を以前の ECMAScript コードに変換できず、プロキシの動作は実際には置き換えられません。言い換えれば、プロキシはプラットフォームでの使用を 100% サポートすることしかできません。
マインドマッピング
エージェントベース
エージェントは、ターゲット オブジェクトの抽象化であり、ターゲット オブジェクトの代わりとして使用できます。エージェントは、ターゲット オブジェクトから完全に独立しています。ターゲット オブジェクトは、直接またはプロキシを通じて操作できます。つまり、ターゲット オブジェクトは、プロキシによって実行される任意の操作です。プロキシ オブジェクトの愚かな女の子は、実際にはターゲット オブジェクトに適用されます。認識できる唯一の違いは、プロキシ オブジェクトがコード内で操作されることです。ただし、プロキシ オブジェクトとターゲット オブジェクトは同じアドレスにアクセスしません。
const target = {
id: "id15",
};
const handlder = {};
const proxy = new Proxy(target, handlder);
// 代理对象和目标对象访问的属性是同名的时候,本质访问的就是同一个值,
// 给目标对象 或者代理对象 属性赋值也会反映到两个对象上
log(proxy.id === target.id); // log: true
target.id = "id_15";
log(proxy.id); // log: id_15
log(target.id); // log: id_15
proxy.id = "pro_id";
log(proxy.id); // log: pro_id
log(target.id); // log: pro_id
// Proxy.rototype 是 undefiend,所以不能使用 instanceof 操作符
log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check
キャッチャー
プロキシを使用する主な目的は、トラップをカスタマイズすることです。キャッチャーは、ハンドラ オブジェクトで定義されたインターセプタです。ハンドラ オブジェクトごとに、0 個以上のキャッチャを定義できます。各キャッチャは、プロキシ オブジェクトで直接または間接的に呼び出すことができる基本操作に対応します。これらの基本操作がプロキシ オブジェクトで呼び出されるたびに、プロキシは、これらの操作がターゲット オブジェクトに伝播される前にキャッチャー関数を呼び出すことによって、対応する動作をインターセプトして変更できます。
すべてのキャッチャーは、独自のパラメータに基づいて元の操作を再構築できます。これは、グローバル Reflect オブジェクトで同じ名前のメソッド (元の動作をカプセル化) を呼び出すことで簡単に再構築できます。つまり、ハンドラーでキャプチャできるすべてのメソッドです。オブジェクトには、キャプチャーによってインターセプトされたメソッドと同じ名前と関数シグネチャを持ち、インターセプトされたメソッドと同じように動作する、対応する Reflect API メソッドがあります。
const proxy = new Proxy(
{ name: "jakequc" },
{
// 捕获器在处理程序对象中以方法名为键
// proxy.property / proxy[property] 等获取属性的时候会出发 get 捕获器
get(target, key, receiver) {
// target 是代理的目标对象,key 获取时的 键(属性),receiver 是代理对象
// log(target, key, receiver);
return target[key] + "__";
},
}
);
log(proxy.name); // log: jakequc__
const reproxy = new Proxy(
{
age: 23,
},
{
// 甚至可以写成 get: Reflect.get
get() {
// 每一个 Proxy 中的拦截器名字 在 Reflect 中都存在,因此可以在 Proxy 拦截器中使用 Reflect
return Reflect.get(...arguments);
},
}
);
log(reproxy.age); // log: 23
すべてのキャプチャ メソッドを持つプロキシを作成し、各メソッドを対応する Reflect API に転送する場合は、Proxy の 2 番目のパラメータを Reflect として直接渡すことができます。
const allReflectPro = new Proxy(
{
name: "kj",
},
Reflect
);
log(allReflectPro.name); // log: kj
キャッチャー不変
使用捕获器几乎可以改变所有基本方法的行为,但是也有限制;虽然每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”,防止捕获器定义出现过于反常行为;如目标对象有一个不可配置且不可写的数据属性,那么捕获器返回一个与该属性不同的值时,会抛出 TypeError
const target = {};
Object.defineProperty(target, "const_key", {
configurable: false, // 不可删除
writable: false, // 不可写入
value: "love", // const_key 的 值为 love 字符串
});
const proxy = new Proxy(target, {
get() {
// 这里违背了 目标对象 const_key 不可写入(更改)的规则,因此在获取对应的值时会触发 TypeError
return "update_love";
},
});
log(proxy.const_key); // TypeError: 'get' on proxy: property 'const_key' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'love' but got 'update_love')
可撤销代理
使用 new Proxy(target, handler) 创建普通代理来说,这种联系会在代理对象的生命周期内一直持续存在,Proxy.revocable(target,handler) 这个方法返回 revoke 方法可以撤销代理操作,值得注意的是,撤销代理的操作是不可逆的且 revoke 方法是幂等的(即调用多少次结果都一样),撤销代理之后再调用代理会抛出 TypeError;
// 代理对象和撤销代理函数在调用 revocable 在实例化同时生成
const { proxy, revoke } = Proxy.revocable(
{
name: "kj",
},
Reflect
);
log(proxy.name); // log: kj
revoke();
// 撤销代理之后,再使用代理就会报 TypeError 错误
log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked