プロキシ

概要

プロキシは、特定の操作のデフォルトの動作を変更するために使用されます。これは、言語レベルで変更を行うことと同等であるため、一種の「メタ プログラミング」、つまりプログラミング言語のプログラミングです。

プロキシは、ターゲット オブジェクトの前に「インターセプト」層を設定するものとして理解できます。そのため、オブジェクトへの外部アクセスは、まずこのインターセプト層を通過する必要があります。したがって、プロキシは、外部アクセスをフィルタリングして書き換えるメカニズムを提供します。プロキシという言葉の本来の意味はエージェントであり、ここでは特定の操作を「代理」するという意味で使用されます。

var obj = 新しいプロキシ({}, {
  get: 関数 (ターゲット、propKey、レシーバー) {
    console.log(`${propKey} を取得しています!`);
    return Reflect.get(ターゲット, propKey, レシーバー);
  }、
  set: 関数 (ターゲット、propKey、値、受信者) {
    console.log(`${propKey} の設定!`);
    return Reflect.set(ターゲット, propKey, 値, レシーバ);
  }
});

上記のコードは、空のオブジェクトにインターセプト層を設定し、プロパティの読み取り (get) および設定 (set) の動作を再定義します。ここでは具体的な構文については説明しません。実行結果を見てください。インターセプト動作を設定してオブジェクト obj のプロパティを読み書きすると、次の結果が得られます。

オブジェクト数 = 1
// 設定数!
++obj.count
// カウントを取得します!
// 設定数!
// 2

上記のコードは、Proxy が実際にドット演算子をオーバーロードしていること、つまり言語の元の定義を独自の定義で上書きしていることを示しています。

ES6 は、プロキシ インスタンスを生成するためのプロキシ コンストラクターをネイティブに提供します。

var proxy = 新しいプロキシ(ターゲット, ハンドラー);

Proxy オブジェクトの使用はすべて上記の形式で行われますが、唯一の違いは handler パラメーターの記述です。このうち、new Proxy()Proxy インスタンスの生成を意味し、target パラメータはインターセプトのターゲットオブジェクトを表し、handler パラメータはインターセプト動作をカスタマイズするために使用されるオブジェクトでもあります。

以下は、プロパティの読み取り動作をインターセプトする別の例です。

var proxy = 新しいプロキシ({}, {
  get: function(target, propKey) {
    35 を返します。
  }
});

プロキシ.タイム // 35
プロキシ名 // 35
proxy.title // 35

上記のコードでは、「Proxy」はコンストラクターとして 2 つのパラメーターを受け取ります。最初のパラメータはプロキシされるターゲット オブジェクトです (上の例は空のオブジェクトです)。つまり、「プロキシ」の介入がない場合、操作によってアクセスされる元のオブジェクトはこのオブジェクトです。エージェント操作の場合、対応する操作をインターセプトする、対応する処理関数を提供する必要があります。たとえば、上記のコードでは、構成オブジェクトには「get」メソッドがあり、これはターゲット オブジェクトのプロパティへのアクセス要求をインターセプトするために使用されます。 「get」メソッドの 2 つのパラメータは、ターゲット オブジェクトとアクセスするプロパティです。ご覧のとおり、インターセプト関数は常に 35 を返すため、どの属性にアクセスしても 35 を取得します。

「Proxy」が機能するには、ターゲット オブジェクト (上記の例は空のオブジェクト) ではなく、「Proxy」 インスタンス (上記の例は「proxy」 オブジェクト) に対して操作を実行する必要があることに注意してください。

handler がインターセプトを設定しない場合、元のオブジェクトへの直接アクセスと同等になります。

varターゲット = {};
var ハンドラー = {};
var proxy = 新しいプロキシ(ターゲット、ハンドラー);
proxy.a = 'b';
target.a // "b"

上記のコードでは、handler はインターセプト効果のない空のオブジェクトです。proxy へのアクセスは、target へのアクセスと同等です。

1 つのトリックは、Proxy オブジェクトを object.proxy プロパティに設定して、object オブジェクト上で呼び出せるようにすることです。

var object = { プロキシ: 新しいプロキシ(ターゲット、ハンドラー) };

プロキシ インスタンスは、他のオブジェクトのプロトタイプ オブジェクトとしても機能します。

var proxy = 新しいプロキシ({}, {
  get: function(target, propKey) {
    35 を返します。
  }
});

let obj = Object.create(プロキシ);
obj.time // 35

上記のコードでは、proxy オブジェクトは obj オブジェクトのプロトタイプであり、obj オブジェクト自体には time 属性がないため、プロトタイプ チェーンに従ってこの属性は で読み取られます。 proxy オブジェクトがインターセプトされる原因となります。

同じインターセプター関数を設定して、複数の操作をインターセプトすることができます。

var ハンドラー = {
  get: function(ターゲット, 名前) {
    if (名前 === 'プロトタイプ') {
      オブジェクト.プロトタイプを返します。
    }
    'こんにちは' + 名前を返します。
  }、

  適用: function(target, thisBinding, args) {
    引数[0]を返します;
  }、

  構築: function(target, args) {
    戻り値: {値: args[1]};
  }
};

var fproxy = 新しいプロキシ(関数(x, y) {
  x + y を返します。
}、ハンドラー);

fproxy(1, 2) // 1
new fproxy(1, 2) // {値: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "こんにちは、foo" // true

設定できるがインターセプトが設定されていない操作の場合、操作はターゲット オブジェクトに直接適用され、元の方法で結果が生成されます。

以下は、Proxy がサポートするインターセプト操作のリストであり、合計 13 種類です。

  • get(target, propKey,receiver): proxy.fooproxy['foo'] などのオブジェクト プロパティの読み取りをインターセプトします。
  • set(target, propKey, value, recruit): proxy.foo = vproxy['foo'] = v などのオブジェクト プロパティの設定をインターセプトし、ブール値を返します。
  • has(target, propKey): propKey in proxy の操作をインターセプトし、ブール値を返します。
  • deleteProperty(target, propKey): delete proxy[propKey] の操作をインターセプトし、ブール値を返します。
  • ownKeys(target): Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in ループをインターセプトし、配列を返します。 。このメソッドはターゲット オブジェクト自身のすべてのプロパティのプロパティ名を返しますが、Object.keys() の戻り結果にはターゲット オブジェクト自身の走査可能なプロパティのみが含まれます。
  • getOwnPropertyDescriptor(target, propKey): Object.getOwnPropertyDescriptor(proxy, propKey) をインターセプトし、プロパティの説明オブジェクトを返します。
  • defineProperty(target, propKey, propDesc): Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs) をインターセプトし、ブール値を返します。
  • preventExtensions(target): Object.preventExtensions(proxy) をインターセプトし、ブール値を返します。
  • getPrototypeOf(target): Object.getPrototypeOf(proxy) をインターセプトし、オブジェクトを返します。
  • isExtensible(target): Object.isExtensible(proxy) をインターセプトし、ブール値を返します。
  • setPrototypeOf(target, proto): Object.setPrototypeOf(proxy, proto) をインターセプトし、ブール値を返します。ターゲット オブジェクトが関数の場合、インターセプトできる操作がさらに 2 つあります。
  • apply(target, object, args): proxy(...args)proxy.call(object, ...args) などの関数呼び出しとして Proxy インスタンスの操作をインターセプトします。 「プロキシ .apply(...)」。
  • construct(target, args): new proxy(...args) などのコンストラクター呼び出しとして Proxy インスタンスの操作をインターセプトします。

プロキシ インスタンスのメソッド

以下では、上記の傍受方法について詳しく説明します。

得る()

「get」メソッドは、特定の属性の読み取り操作をインターセプトするために使用されます。このメソッドは、ターゲット オブジェクト、属性名、プロキシ インスタンス自体 (厳密には、操作動作の対象となるオブジェクト) の 3 つのパラメーターを受け取ることができます。 ) 最後のパラメータは選択できます。

上記の「get」メソッドの使用例は、読み取り操作をインターセプトする別の例です。

var 人 = {
  名前:「張三」
};

var proxy = 新しいプロキシ(人, {
  get: function(target, propKey) {
    if (ターゲット内のpropKey) {
      ターゲット[propKey]を返します;
    } それ以外 {
      throw new ReferenceError("プロップ名 \"" + propKey + "\" が存在しません。");
    }
  }
});

proxy.name // "張三"
proxy.age // エラーがスローされます

上記のコードは、ターゲット オブジェクトに存在しないプロパティにアクセスするとエラーがスローされることを示しています。このインターセプト関数がないと、存在しないプロパティにアクセスすると、「未定義」のみが返されます。

getメソッドは継承可能です。

let proto = 新しいプロキシ({}, {
  get(ターゲット、プロパティキー、レシーバー) {
    console.log('GET ' + propertyKey);
    ターゲット[プロパティキー]を返します;
  }
});

obj = Object.create(proto); とします。
obj.foo // "fooを取得"

上記のコードでは、インターセプト操作が Prototype オブジェクトで定義されているため、obj オブジェクトによって継承されたプロパティが読み取られると、インターセプトが有効になります。

次の例では、「get」インターセプトを使用して、配列の負のインデックスを読み取ります。

function createArray(...elements) {
  let ハンドラー = {
    get(ターゲット、propKey、受信者) {
      let インデックス = Number(propKey);
      if (インデックス < 0) {
        propKey = 文字列(ターゲットの長さ + インデックス);
      }
      return Reflect.get(ターゲット, propKey, レシーバー);
    }
  };

  ターゲット = []; にします。
  target.push(...要素);
  新しいプロキシ(ターゲット、ハンドラー)を返します。
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

上記のコードでは、配列の位置パラメータが -1 の場合、配列の最後のメンバーが出力されます。

Proxyを利用すると、属性の読み込み(get)を関数の実行に変換することができ、属性の連鎖操作を実現できます。

var パイプ = 関数 (値) {
  var funcStack = [];
  var oproxy = 新しいプロキシ({}, {
    get : function (pipeObject, fnName) {
      if (fnName === 'get') {
        return funcStack.reduce(function (val, fn) {
          fn(val) を返します。
        }、価値);
      }
      funcStack.push(window[fnName]);
      オプロキシを返します。
    }
  });

  オプロキシを返します。
}

var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") 0;

パイプ(3).double.pow.reverseInt.get; // 63

上記のコードでプロキシを設定すると、チェーン内で関数名を使用する効果が得られます。

次の例では、get インターセプトを使用して、さまざまな DOM ノードを生成する汎用関数 dom を実装します。

const dom = 新しいプロキシ({}, {
  get(ターゲット, プロパティ) {
    return function(attrs = {}, ...children) {
      const el = document.createElement(プロパティ);
      for (Object.keys(attrs) の prop を許可) {
        el.setAttribute(prop, attrs[prop]);
      }
      for (子の子にする) {
        if (子の種類 === '文字列') {
          child = document.createTextNode(child);
        }
        el.appendChild(子);
      }
      エルを返します。
    }
  }
});

const el = dom.div({},
  「こんにちは、私の名前は 」、
  dom.a({href: '//example.com'}, 'マーク'),
  「私は好きです:」、
  dom.ul({},
    dom.li({}, 'ウェブ'),
    dom.li({}, '食品'),
    dom.li({}, '…実際にはそれだけです')
  )
);

document.body.appendChild(el);

以下は、「get」メソッドの 3 番目のパラメータの例です。これは常に、元の読み取り操作が配置されているオブジェクト (通常は Proxy インスタンス) を指します。

const proxy = 新しいプロキシ({}, {
  get: function(ターゲット、キー、レシーバー) {
    リターンレシーバー。
  }
});
proxy.getReceiver === プロキシ // true

上記のコードでは、proxy オブジェクトの getReceiver 属性が get() によってインターセプトされ、戻り値は proxy オブジェクトになります。

const proxy = 新しいプロキシ({}, {
  get: function(ターゲット、キー、レシーバー) {
    リターンレシーバー。
  }
});

const d = Object.create(プロキシ);
d.a === d // true

上記のコードでは、d オブジェクト自体には a 属性がないため、d.a を読み取るときに、d のプロトタイプの proxy オブジェクトが検索されます。このとき、「receiver」は、元の読み取り操作が配置されたオブジェクトを表す「d」を指します。

プロパティが構成可能および書き込み可能でない場合、プロキシはプロパティを変更できません。変更しないと、プロキシ オブジェクトを介してプロパティにアクセスするときにエラーが報告されます。

const target = Object.defineProperties({}, {
  フー: {
    値: 123、
    書き込み可能: false、
    設定可能: false
  }、
});

const ハンドラ = {
  get(ターゲット, propKey) {
    「abc」を返します;
  }
};

const proxy = 新しいプロキシ(ターゲット, ハンドラー);

proxy.foo
// TypeError: 不変チェックに失敗しました

セット()

set メソッドは、特定の属性の割り当て操作をインターセプトするために使用されます。ターゲット オブジェクト、属性名、属性値、およびプロキシ インスタンス自体の 4 つのパラメーターを受け入れることができます。

person オブジェクトが 200 以下の整数である age 属性を持っていると仮定すると、p​​roxy を使用して age 属性値が要件を満たしていることを確認できます。

let バリデータ = {
  セット: function(obj, prop, value) {
    if (prop === '年齢') {
      if (!Number.isInteger(value)) {
        throw new TypeError('年齢は整数ではありません');
      }
      if (値 > 200) {
        throw new RangeError('年齢が無効のようです');
      }
    }

    // 年齢属性など条件に合った属性は直接保存
    obj[prop] = 値;
    true を返します。
  }
};

let person = new Proxy({}, バリデーター);

人.年齢 = 100;

person.age // 100
person.age = 'young' // エラーレポート
person.age = 300 // エラーレポート

上記のコードでは、値の設定関数「set」により、要件を満たさない「age」属性を代入するとエラーが発生するデータ検証の実装方法です。 「set」メソッドを使用すると、データ バインディングも使用できます。つまり、オブジェクトが変更されるたびに、DOM が自動的に更新されます。

場合によっては、オブジェクトの内部プロパティを設定することがあります。プロパティ名の最初の文字はアンダースコアで始まり、これらのプロパティを外部で使用しないことを示します。 「get」メソッドと「set」メソッドを組み合わせると、これらの内部プロパティが外部から読み書きされるのを防ぐことができます。

const ハンドラ = {
  get (ターゲット, キー) {
    invariant(key, 'get');
    ターゲット[キー]を返します;
  }、
  set (ターゲット、キー、値) {
    invariant(key, 'set');
    ターゲット[キー] = 値;
    true を返します。
  }
};
関数不変式 (キー、アクション) {
  if (key[0] === '_') {
    throw new Error(`${action} プライベート "${key}" プロパティへの無効な試行`);
  }
}
const ターゲット = {};
const proxy = 新しいプロキシ(ターゲット, ハンドラー);
proxy._prop
// エラー: プライベート "_prop" プロパティを取得しようとしたのは無効です
proxy._prop = 'c'
// エラー: プライベート "_prop" プロパティを設定しようとしたのは無効です

上記コードでは、読み書きする属性名の先頭文字がアンダースコアであればエラーとなり、内部属性の読み書き禁止の目的は達成されます。

以下は、set メソッドの 4 番目のパラメーターの例です。

const ハンドラ = {
  set: function(obj, prop, value, レシーバ) {
    obj[prop] = 受信機;
    true を返します。
  }
};
const proxy = new Proxy({}, ハンドラー);
proxy.foo = 'バー';
proxy.foo === プロキシ // true

上記のコードでは、set メソッドの 4 番目のパラメーター receiver は、元の操作動作が配置されているオブジェクトを参照します。以下の例を参照してください。

const ハンドラ = {
  set: function(obj, prop, value, レシーバ) {
    obj[prop] = 受信機;
    true を返します。
  }
};
const proxy = new Proxy({}, ハンドラー);
const myObj = {};
Object.setPrototypeOf(myObj, プロキシ);

myObj.foo = 'バー';
myObj.foo === myObj // true

上記のコードでは、myObj.foo プロパティの値を設定するときに、myObj には foo プロパティがないため、エンジンは myObj のプロトタイプ チェーンに移動して foo プロパティを見つけます。 。 myObj のプロトタイプ オブジェクト proxy は Proxy インスタンスであり、その foo プロパティを設定すると set メソッドがトリガーされます。このとき、4 番目のパラメーター 'receiver' は、元の割り当て動作が配置されているオブジェクト 'myObj' を指します。

ターゲットオブジェクト自体のプロパティが書き込み可能でない場合、set メソッドは機能しないことに注意してください。

const obj = {};
Object.defineProperty(obj, 'foo', {
  値: 'バー'、
  書き込み可能: false
});

const ハンドラ = {
  set: function(obj, prop, value, レシーバー) {
    obj[prop] = 'バズ';
    true を返します。
  }
};

const proxy = 新しいプロキシ(obj, ハンドラー);
proxy.foo = 'バズ';
proxy.foo // "バー"

上記のコードでは、「obj.foo」プロパティは書き込み可能ではないため、このプロパティの「set」プロキシは有効になりません。

「set」プロキシはブール値を返す必要があることに注意してください。厳密モードでは、「set」エージェントが「true」を返さない場合、エラーが報告されます。

'厳密を使用';
const ハンドラ = {
  set: function(obj, prop, value, レシーバー) {
    obj[prop] = 受信機;
    // 以下の行があってもなくてもエラーが報告されます
    false を返します。
  }
};
const proxy = new Proxy({}, ハンドラー);
proxy.foo = 'バー';
// TypeError: プロキシの 'set': プロパティ 'foo' に対してトラップが false を返しました

上記のコードでは、厳密モードで、set プロキシが false または unknown を返すと、エラーが報告されます。

適用する()

「apply」メソッドは、関数呼び出し、「call」および「apply」操作をインターセプトします。

「apply」メソッドは、ターゲット オブジェクト、ターゲット オブジェクトのコンテキスト オブジェクト (「this」)、およびターゲット オブジェクトのパラメーター配列の 3 つのパラメーターを受け入れることができます。

var ハンドラー = {
  apply (ターゲット、ctx、args) {
    return Reflect.apply(...arguments);
  }
};

以下に例を示します。

var target = function () { return '私がターゲットです' };
var ハンドラー = {
  適用: function () {
    「私が代理人です」を返します。
  }
};

var p = 新しいプロキシ(ターゲット、ハンドラー);

p()
// 「私が代理人です」

上記のコードでは、変数 p は Proxy のインスタンスであり、関数 (p()) として呼び出されると、apply メソッドによってインターセプトされ、文字列が返されます。

別の例を示します。

var2 回 = {
  apply (ターゲット、ctx、args) {
    return Reflect.apply(...arguments) * 2;
  }
};
関数 sum (左、右) {
  左 + 右を返します。
};
var proxy = 新しいプロキシ(合計、2 回);
プロキシ(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30

上記のコードでは、proxy 関数が実行されるたびに (直接呼び出し、または call および apply 呼び出し)、apply メソッドによってインターセプトされます。

さらに、「Reflect.apply」メソッドを直接呼び出すこともインターセプトされます。

Reflect.apply(proxy, null, [9, 10]) // 38

もっている()

has() メソッドは、HasProperty 操作をインターセプトするために使用されます。つまり、オブジェクトが特定のプロパティを持っているかどうかを判断するときに、このメソッドが有効になります。典型的な演算は「in」演算子です。

has() メソッドは、ターゲット オブジェクトとクエリ対象の属性名という 2 つのパラメータを受け入れることができます。

次の例では、has() メソッドを使用して、特定のプロパティが in 演算子によって検出されないように隠します。

var ハンドラー = {
  has (ターゲット、キー) {
    if (key[0] === '_') {
      false を返します。
    }
    ターゲット内のキーを返します。
  }
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = 新しいプロキシ(ターゲット, ハンドラー);
プロキシの '_prop' // false

上記のコードでは、元のオブジェクトの属性名の最初の文字がアンダースコアの場合、proxy.has()false を返し、in 演算子によって検出されません。

元のオブジェクトが設定可能でない場合、または拡張が禁止されている場合、has() インターセプトはエラーを報告します。

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = 新しいプロキシ(obj, {
  持っています: function(target, prop) {
    false を返します。
  }
});

p の 'a' // TypeError がスローされる

上記のコードでは、obj オブジェクトの展開が禁止されているため、has を使用してインターセプトするとエラーが報告されます。つまり、プロパティが設定可能でない場合 (またはターゲット オブジェクトが拡張可能でない場合)、has() メソッドはターゲット オブジェクトのそのプロパティを「非表示」にしてはなりません(つまり、false を返します)。

has() メソッドは、HasOwnProperty 操作ではなく、HasProperty 操作をインターセプトすることに注意してください。つまり、 has() メソッドは、プロパティがオブジェクト自身のプロパティであるか継承されたプロパティであるかを判断しません。財産。

さらに、for...in ループでも in 演算子が使用されますが、has() インターセプトは for...in ループには影響しません。

let stu1 = {名前: '张三'、スコア: 59};
let stu2 = {名前: '李思'、スコア: 99};

let ハンドラー = {
  has(ターゲット、プロップ) {
    if (prop === 'スコア' && target[prop] < 60) {
      console.log(`${target.name} が失敗しました`);
      false を返します。
    }
    ターゲット内のプロップを返します。
  }
}

let oproxy1 = 新しいプロキシ(stu1, ハンドラー);
let oproxy2 = 新しいプロキシ(stu2, ハンドラー);

oproxy1 の「スコア」
// 張三は失敗しました
// 間違い

oproxy2 の「スコア」
// 真実

for (oproxy1 に a を入れます) {
  console.log(oproxy1[a]);
}
// 張三
// 59

for (oproxy2 に b を入れます) {
  console.log(oproxy2[b]);
}
// ジョン・ドゥ
//99

上記のコードでは、has() インターセプトは in 演算子にのみ有効であり、for...in ループには無効です。その結果、属性は要件を満たしていません。 for...in ループによって除外されません。

構築()

new コマンドをインターセプトするには、construct() メソッドを使用します。インターセプト オブジェクトの記述方法は次のとおりです。

const ハンドラ = {
  構築 (ターゲット、引数、newTarget) {
    新しいターゲット(...args)を返します;
  }
};

construct() メソッドは 3 つのパラメータを受け入れることができます。

  • target: ターゲットオブジェクト。
  • args: コンストラクターのパラメーターの配列。
  • newTarget: インスタンス オブジェクト (以下の例では p) を作成するときに new コマンドによって使用されるコンストラクター。
const p = new Proxy(function () {}, {
  構築: function(target, args) {
    console.log(' と呼ばれる: ' + args.join(', '));
    戻り値: args[0] * 10 };
  }
});

(新しい p(1)).value
// "呼び出されました: 1"
// 10

construct() メソッドはオブジェクトを返さなければなりません。そうでない場合はエラーが報告されます。

const p = new Proxy(function() {}, {
  構築: function(target, argumentList) {
    1を返します。
  }
});

new p() // エラーを報告する
// キャッチされない TypeError: プロキシの 'construct': トラップが非オブジェクト ('1') を返しました

さらに、construct() はコンストラクターをインターセプトするため、そのターゲット オブジェクトは関数である必要があり、そうでない場合はエラーが報告されます。

const p = 新しいプロキシ({}, {
  構築: function(target, argumentList) {
    戻る {};
  }
});

new p() // エラーを報告する
// キャッチされない TypeError: p はコンストラクターではありません

上記の例では、インターセプトされたターゲット オブジェクトは関数ではなくオブジェクト (new Proxy() の最初のパラメータ) であるため、エラーが発生します。

construct() メソッドの this はインスタンス オブジェクトではなく handler を指していることに注意してください。

const ハンドラ = {
  構築: function(target, args) {
    console.log(この === ハンドラー);
    新しいターゲット(...args)を返します;
  }
}

let p = new Proxy(function () {}, ハンドラー);
new p() // true

deleteProperty()

deleteProperty メソッドは、delete 操作をインターセプトするために使用されます。このメソッドがエラーをスローするか、false を返す場合、現在のプロパティは delete コマンドで削除できません。

var ハンドラー = {
  deleteProperty (ターゲット、キー) {
    invariant(key, '削除');
    ターゲット[キー]を削除します。
    true を返します。
  }
};
関数不変式 (キー、アクション) {
  if (key[0] === '_') {
    throw new Error(`${action} プライベート "${key}" プロパティへの無効な試行`);
  }
}

var target = { _prop: 'foo' };
var proxy = 新しいプロキシ(ターゲット, ハンドラー);
proxy._prop を削除します
// エラー: プライベート "_prop" プロパティを削除しようとしたのは無効です

上記のコードでは、deleteProperty メソッドが delete 演算子をインターセプトし、最初の文字がアンダースコアであるプロパティを削除するとエラーが報告されます。

ターゲット オブジェクト自体の構成不可能なプロパティは、deleteProperty メソッドでは削除できないことに注意してください。削除しないと、エラーが報告されます。

###defineProperty()

defineProperty() メソッドは、Object.defineProperty() オペレーションをインターセプトします。

var ハンドラー = {
  defineProperty (ターゲット、キー、記述子) {
    false を返します。
  }
};
varターゲット = {};
var proxy = 新しいプロキシ(ターゲット, ハンドラー);
proxy.foo = 'bar' // 有効になりません

上記のコードでは、defineProperty() メソッド内に操作はなく、false のみを返すため、新しいプロパティの追加は常に無効になります。ここでの「false」は、操作が失敗したことを示すためにのみ使用されており、それ自体が新しい属性の追加を妨げるものではないことに注意してください。

ターゲット オブジェクトが拡張不可能な場合、defineProperty() はターゲット オブジェクトに存在しないプロパティを追加できないことに注意してください。追加しない場合は、エラーが報告されます。さらに、ターゲット オブジェクトのプロパティが書き込み可能または構成可能でない場合、defineProperty() メソッドはこれら 2 つの設定を変更してはなりません。

getOwnPropertyDescriptor()

getOwnPropertyDescriptor() メソッドは Object.getOwnPropertyDescriptor() をインターセプトし、プロパティ記述オブジェクトまたは unknown を返します。

var ハンドラー = {
  getOwnPropertyDescriptor (ターゲット、キー) {
    if (key[0] === '_') {
      戻る;
    }
    return Object.getOwnPropertyDescriptor(ターゲット, キー);
  }
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = 新しいプロキシ(ターゲット, ハンドラー);
Object.getOwnPropertyDescriptor(プロキシ, 'ワット')
// 未定義
Object.getOwnPropertyDescriptor(プロキシ, '_foo')
// 未定義
Object.getOwnPropertyDescriptor(プロキシ, 'baz')
// { 値: 'tar'、書き込み可能: true、列挙可能: true、構成可能: true }

上記のコードでは、最初の文字がアンダースコアであるプロパティ名の場合、handler.getOwnPropertyDescriptor() メソッドは unknown を返します。

getPrototypeOf()

getPrototypeOf() メソッドは主にオブジェクトのプロトタイプをインターセプトして取得するために使用されます。具体的には、以下の操作を傍受します。

  • Object.prototype.__proto__
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • インスタンス

以下に例を示します。

var proto = {};
var p = 新しいプロキシ({}, {
  getPrototypeOf(ターゲット) {
    プロトを返します。
  }
});
Object.getPrototypeOf(p) === プロト // true

上記のコードでは、getPrototypeOf() メソッドが Object.getPrototypeOf() をインターセプトし、proto オブジェクトを返します。

getPrototypeOf() メソッドの戻り値はオブジェクトまたは null でなければならないことに注意してください。そうでない場合はエラーが報告されます。さらに、ターゲット オブジェクトが拡張不可能な場合、getPrototypeOf() メソッドはターゲット オブジェクトのプロトタイプ オブジェクトを返さなければなりません。

isExtensible()

isExtensible() メソッドは Object.isExtensible() オペレーションをインターセプトします。

var p = 新しいプロキシ({}, {
  isExtensible: 関数(ターゲット) {
    console.log("呼び出された");
    true を返します。
  }
});

Object.isExtensible(p)
// 「呼ばれた」
// 真実

上記のコードは isExtensible() メソッドを設定しており、Object.isExtensible が呼び出されたときに called が出力されます。

このメソッドはブール値のみを返すことができ、それ以外の場合、戻り値は自動的にブール値に変換されることに注意してください。

このメソッドには強力な制限があり、その戻り値はターゲット オブジェクトの isExtensible プロパティと一致する必要があり、そうでない場合はエラーがスローされます。

Object.isExtensible(プロキシ) === Object.isExtensible(ターゲット)

以下に例を示します。

var p = 新しいプロキシ({}, {
  isExtensible: 関数(ターゲット) {
    false を返します。
  }
});

Object.isExtensible(p)
// Uncaught TypeError: プロキシの 'isExtensible': トラップ結果はプロキシ ターゲットの拡張性を反映しません (これは 'true')

ownKeys()

ownKeys() メソッドは、オブジェクト自身のプロパティの読み取り操作をインターセプトするために使用されます。具体的には、以下の操作を傍受します。

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in ループ

以下は Object.keys() をインターセプトする例です。

ターゲット = { にします
  答え: 1、
  b:2、
  c:3
};

let ハンドラー = {
  ownKeys(ターゲット) {
    ['a'] を返します。
  }
};

let proxy = new Proxy(ターゲット, ハンドラー);

オブジェクト.キー(プロキシ)
// [ 'a' ]

上記のコードは、target オブジェクトに対する Object.keys() 操作をインターセプトし、3 つの属性 ab、および c のうちの a 属性のみを返します。

次の例では、最初の文字がアンダースコアであるプロパティ名をインターセプトします。

ターゲット = { にします
  _bar: 'フー',
  _prop: 'バー',
  小道具: 「バズ」
};

let ハンドラー = {
  ownKeys (ターゲット) {
    return Reflect.ownKeys(target).filter(key => key[0] !== '_');
  }
};

let proxy = new Proxy(ターゲット, ハンドラー);
for (Object.keys(proxy) のキーを許可) {
  console.log(ターゲット[キー]);
}
// "バズ"

Object.keys() メソッドを使用する場合、ownKeys() メソッドによって自動的にフィルタリングされ、返されない 3 種類のプロパティがあることに注意してください。

  • 対象オブジェクトに存在しないプロパティ
  • シンボル値という名前のプロパティ
  • 走査不可能 (列挙可能) プロパティ
ターゲット = { にします
  答え: 1、
  b:2、
  c:3、
  [Symbol.for('secret')]: '4',
};

Object.defineProperty(ターゲット, 'キー', {
  列挙可能: false、
  構成可能: true、
  書き込み可能: true、
  値: '静的'
});

let ハンドラー = {
  ownKeys(ターゲット) {
    return ['a', 'd', Symbol.for('secret'), 'key'];
  }
};

let proxy = new Proxy(ターゲット, ハンドラー);

オブジェクト.キー(プロキシ)
//['a']

上記のコードでは、ownKeys() メソッドは、存在しないプロパティ (d)、シンボル値 (Symbol.for('secret'))、および通過不可能なプロパティ (key) を明示的に返します。 ) の場合、結果は自動的に除外されます。

ownKeys() メソッドは Object.getOwnPropertyNames() をインターセプトすることもできます。

var p = 新しいプロキシ({}, {
  ownKeys: 関数(ターゲット) {
    ['a''b''c'] を返します。
  }
});

Object.getOwnPropertyNames(p)
// [ 'a', 'b', 'c' ]

「for...in」ループも、「ownKeys()」メソッドによってインターセプトされます。

const obj = { こんにちは: '世界' };
const proxy = 新しいプロキシ(obj, {
  ownKeys: function () {
    ['a', 'b'] を返します。
  }
});

for (キーをプロキシに入れる) {
  console.log(key); // 出力なし
}

上記のコードでは、ownkeys() は、a および b 属性のみが返されることを指定しています。obj にはこれら 2 つの属性がないため、for...in ループは出力を生成しません。 。

ownKeys() メソッドによって返される配列メンバーは、文字列値またはシンボル値のみです。他のタイプの値がある場合、または戻り値が配列ではない場合は、エラーが報告されます。

var obj = {};

var p = 新しいプロキシ(obj, {
  ownKeys: 関数(ターゲット) {
    return [123true、未定義、null、{}、[]];
  }
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 123 は有効なプロパティ名ではありません

上記のコードでは、ownKeys() メソッドは配列を返しますが、配列の各メンバーは文字列またはシンボル値ではないため、エラーが報告されます。

ターゲット オブジェクト自体に構成不可能なプロパティが含まれている場合、そのプロパティは ownKeys() メソッドによって返される必要があります。そうでない場合は、エラーが報告されます。

var obj = {};
Object.defineProperty(obj, 'a', {
  構成可能: false、
  列挙可能: true、
  値: 10 }
);

var p = 新しいプロキシ(obj, {
  ownKeys: 関数(ターゲット) {
    ['b'] を返します。
  }
});

Object.getOwnPropertyNames(p)
// キャッチされない TypeError: プロキシ上の 'ownKeys': トラップ結果には 'a' が含まれませんでした

上記のコードでは、「obj」オブジェクトの「a」属性は、現時点では構成可能ではありません。「ownKeys()」メソッドによって返される配列には「a」が含まれている必要があります。そうでない場合は、エラーが報告されます。

さらに、ターゲット オブジェクトが拡張不可能な場合、ownKeys() メソッドによって返される配列には、元のオブジェクトのすべての属性が含まれている必要があり、冗長な属性を含めることはできません。そうでない場合は、エラーが報告されます。

var obj = {
  答え: 1
};

Object.preventExtensions(obj);

var p = 新しいプロキシ(obj, {
  ownKeys: 関数(ターゲット) {
    ['a', 'b'] を返します。
  }
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: トラップは追加のキーを返しましたが、プロキシ ターゲットは拡張できません

上記のコードでは、obj オブジェクトは拡張可能ではないため、ownKeys() メソッドによって返される配列には、obj オブジェクトの冗長な属性 b が含まれているため、エラーが発生します。

###PreventExtensions()

preventExtensions() メソッドは Object.preventExtensions() をインターセプトします。このメソッドはブール値を返す必要があります。それ以外の場合は、ブール値に自動的に変換されます。

このメソッドには制限があります。ターゲット オブジェクトが拡張できない場合 (つまり、Object.isExtensible(proxy)false である場合)、proxy.preventExtensionstrue を返すことができます。それ以外の場合は、エラーが報告されます。

var proxy = 新しいプロキシ({}, {
  PreventExtensions: function(target) {
    true を返します。
  }
});

Object.preventExtensions(プロキシ)
// Uncaught TypeError: プロキシ上の 'preventExtensions': トラップは truish を返しましたが、プロキシ ターゲットは拡張可能です

上記コードでは「proxy.preventExtensions()」メソッドが「true」を返しますが、この時点では「Object.isExtensible(proxy)」が「true」を返すためエラーが報告されます。

この問題を回避するには、通常、proxy.preventExtensions() メソッド内で Object.preventExtensions() を 1 回呼び出す必要があります。

var proxy = 新しいプロキシ({}, {
  PreventExtensions: function(target) {
    console.log('呼び出された');
    Object.preventExtensions(ターゲット);
    true を返します。
  }
});

Object.preventExtensions(プロキシ)
// 「呼ばれた」
// プロキシ {}

setPrototypeOf()

setPrototypeOf() メソッドは主に Object.setPrototypeOf() メソッドをインターセプトするために使用されます。

以下に例を示します。

var ハンドラー = {
  setPrototypeOf (ターゲット, プロト) {
    throw new Error('プロトタイプの変更は禁止されています');
  }
};
var proto = {};
var target = function () {};
var proxy = 新しいプロキシ(ターゲット, ハンドラー);
Object.setPrototypeOf(プロキシ, プロト);
// エラー: プロトタイプの変更は禁止されています

上記のコードでは、「target」のプロトタイプ オブジェクトが変更されている限り、エラーが報告されます。

このメソッドはブール値のみを返すことができ、それ以外の場合は自動的にブール値に変換されることに注意してください。さらに、ターゲット オブジェクトが拡張不可能な場合、setPrototypeOf() メソッドはターゲット オブジェクトのプロトタイプを変更してはなりません。

Proxy.revocable()

Proxy.revocable() メソッドは、取り消し可能な Proxy インスタンスを返します。

ターゲット = {} にします。
let ハンドラー = {};

let {proxy, revoke} = Proxy.revocable(ターゲット, ハンドラー);

プロキシ.foo = 123;
proxy.foo // 123

取り消す();
proxy.foo // TypeError: 取り消されました

Proxy.revocable() メソッドは、proxy プロパティが Proxy インスタンスであるオブジェクトを返し、 revoke プロパティは Proxy インスタンスをキャンセルできる関数です。上記のコードでは、「revoke」関数が実行され、「Proxy」インスタンスにアクセスすると、エラーがスローされます。

Proxy.revocable() の使用シナリオの 1 つは、ターゲット オブジェクトへの直接アクセスが許可されておらず、プロキシ経由でアクセスする必要があることです。アクセスが完了すると、プロキシ権限が取り消され、それ以上のアクセスは許可されなくなります。

この質問

プロキシはターゲット オブジェクトへのアクセスを代理できますが、ターゲット オブジェクトの透過的なプロキシではありません。つまり、傍受なしでターゲット オブジェクトとの一貫した動作を保証することはできません。主な理由は、Proxy プロキシの場合、ターゲット オブジェクト内の this キーワードが Proxy プロキシを指すことです。

const ターゲット = {
  m: 関数 () {
    console.log(この === プロキシ);
  }
};
const ハンドラー = {};

const proxy = 新しいプロキシ(ターゲット, ハンドラー);

target.m() // false
proxy.m() // true

上記のコードでは、proxytarget をプロキシすると、target.m() 内の thistarget ではなく proxy を指します。したがって、proxy はインターセプトを行いませんが、target.m()proxy.m() は異なる結果を返します。

以下は、「this」が指す変更により、Proxy がターゲット オブジェクトをプロキシできない例です。

const _name = 新しい WeakMap();

クラス人 {
  コンストラクター(名前) {
    _name.set(これ, 名前);
  }
  名前を取得() {
    return _name.get(this);
  }
}

const jane = 新しい人('ジェーン');
jane.name // 'ジェーン'

const proxy = 新しいプロキシ(jane, {});
proxy.name // 未定義

上記のコードでは、ターゲット オブジェクト janename 属性は、実際には外部の WeakMap オブジェクト _name に格納されており、this キーによって区別されます。 「proxy.name」でアクセスした場合、「this」は「proxy」を指しているため値が取得できず、「unknown」が返されます。

さらに、ネイティブ オブジェクトの一部の内部プロパティは、正しい「this」を通じてのみ取得できるため、プロキシはこれらのネイティブ オブジェクトのプロパティをプロキシすることはできません。

const target = new Date();
const ハンドラー = {};
const proxy = 新しいプロキシ(ターゲット, ハンドラー);

プロキシ.getDate();
// TypeError: これは Date オブジェクトではありません。

上記のコードでは、「getDate()」メソッドは「Date」オブジェクト インスタンスでのみ取得できます。「this」が「Date」オブジェクト インスタンスでない場合、エラーが報告されます。この時、thisを元のオブジェクトにバインドすることでこの問題は解決できます。

const target = new Date('2015-01-01');
const ハンドラ = {
  get(ターゲット、プロップ) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    戻り値 Reflect.get(ターゲット, プロパティ);
  }
};
const proxy = 新しいプロキシ(ターゲット, ハンドラー);

proxy.getDate() // 1

さらに、プロキシは、「handler」オブジェクトを指す関数内の「this」をインターセプトします。

const ハンドラ = {
  get: 関数 (ターゲット、キー、レシーバー) {
    console.log(この === ハンドラー);
    'こんにちは' + キーを返します。
  }、
  set: 関数 (ターゲット、キー、値) {
    console.log(この === ハンドラー);
    ターゲット[キー] = 値;
    true を返します。
  }
};

const proxy = new Proxy({}, ハンドラー);

proxy.foo
// 真実
// こんにちは、ふーちゃん

プロキシ.foo = 1
// 真実

上の例では、get()set() が関数内の this をインターセプトし、handler オブジェクトを指します。

例: Web サービスクライアント

Proxy オブジェクトはターゲット オブジェクトのあらゆる属性をインターセプトできるため、Web サービスのクライアントの作成に適しています。

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const 従業員 = JSON.parse(json);
  //···
});

上記のコードは、さまざまなデータを返す新しい Web サービス インターフェイスを作成します。プロキシはこのオブジェクトの任意の属性をインターセプトできるため、データの種類ごとに適応メソッドを記述する必要はなく、プロキシ インターセプトを記述するだけです。

関数 createWebService(baseUrl) {
  新しいプロキシを返します({}, {
    get(ターゲット、propKey、受信者) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

同様に、プロキシを使用してデータベースの ORM 層を実装することもできます。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0