Proxy

Proxyとは

これらは基本的な言語操作(例えば、プロパティ検索、代入、列挙、関数呼び出しなど)に割り込み、動作をカスタマイズできます。
メタプログラミングより

らしいです。自分の理解で説明すると、「プロパティ値に実際にアクセスする前にプロパティ値を好きに弄れる。元の値も返そうと思えば返せる」というような感じです。

Proxy Handler

new Proxy(obj, handler)のように第2引数に渡します。ハンドラには特定のメソッドをいくつか作る必要があります。例えば、

  • 値の取得時get
  • 値の設定時set
  • in構文でhas
  • 関数実行時apply
  • newした時constructor

など。そしてこれらの事をトラップ(trap)と言います。

Reflect について

Reflectを使うとgettersetterなど関数の中のthisをProxytargetのように扱うことができます。

まずはgetsetだけ見ていきます。

getトラップ

const realObj = {
  foo: 'foo',
  get bar() {
    return this._bar;
  }
};

const handler = {
  get(target, propName, receiver) {
    if (target[propName]) {
      return Reflect.get(...arguments);
    }
    return 'default value';
  },
}

const obj = new Proxy(realObj, handler);
console.log(obj.foo, obj.bar); // 'foo' 'default value'

これはrealObjgetトラップを適用してます。obj.fooの時はそのまま値が返りますが、obj.barの時はthis._barという値がないのでdefault valueが返ります。

setトラップ

まずsetトラップは値を入れたか入れないかでbooleanを返すようにします。

const realObj = {
  foo: 0,
  _bar: 0,
  set bar(value) {
    this._bar = value;
  },
  set baz(value) {
    this._baz = value;
  }
};

const handler = {
  set(target, propName, value, receiver) {
    if (Object.prototype.hasOwnProperty.call(target, propName)) {
      Reflect.set(...arguments);
      return true;
    }
    return false;
  },
}

const obj = new Proxy(realObj, handler);
obj.foo = obj.bar = obj.baz = 1;
console.log(obj.foo, obj._bar, obj._baz) // 1 1 undefined

これはfoo_barには1が入りますが、_barは入りません。これはthis._bazが無いためです。