🐋

【前半】ProxyオブジェクトとReflectオブジェクト

2022/03/07に公開

どうもフロントエンドエンジニアのoreoです。2回に分けてProxyオブジェクトとReflectオブジェクトに関して整理したいと思います。前半のこの記事では、Proxyオブジェクトに関して記載します。

1 Proxyオブジェクトとは?

プロパティーの操作(取得など)に独自の処理を追加するためのオブジェクトです。

基本的な構文として、new Proxy()の第一引数に独自処理を追加したいオブジェクト、第二引数には第一引数に渡したオブジェクトに対して操作するメソッドを格納したオブジェクト(ハンドラーと呼ばれます)を渡します。

/**
 * @param target   独自処理を追加したいオブジェクト
 * @param handler  targetに対して操作するメソッドを格納したオブジェクト
 */
const proxy = new Proxy(target, handler);

ハンドラー内で使用するメソッドは、トラップと呼ばれ主に下記のようなものがあります。

1-1 setトラップ

プロパティーの更新が行われた時に独自の処理を追加できます。setトラップには、targetpropvaluereceiverの4つの引数を渡します。以下の例では、プロパティーaを更新しようとすると、console.logが呼ばれます。

const targetObj = { a: 0 };

/**
 * @param target    new Proxy()の第一引数に渡される独自処理を追加したいオブジェクト
 * @param prop      アクセスされたプロパティーの名前
 * @param value     更新したい値
 * @param receiver  new Proxy()でインスタンス化されたオブジェクト
 */
const handler = {
  set: function (target, prop, value, receiver) {
    console.log(`${prop}${value}に変更。setトラップ発動`);
    target[prop] = value;  //値を更新
  }
};

const proxy = new Proxy(targetObj, handler);
proxy.a = 100;           //「aを100に変更。setトラップ発動」が出力

1-2 getトラップ

プロパティーの取得が行われた時に独自の処理を追加できます。getトラップには、targetpropreceiverの3つの引数を渡します。以下の例では、プロパティーaにアクセスすると、console.logが呼ばれます。

const targetObj = { a: 0 };

/**
 * @param target    new Proxy()の第一引数に渡される独自処理を追加したいオブジェクト
 * @param prop      アクセスされたプロパティーの名前
 * @param receiver  new Proxy()でインスタンス化されたオブジェクト
 */
const handler = {
  get: function (target, prop, receiver) {
    console.log(`${prop}にアクセス。getトラップ発動`);
    return target[prop];  //アクセスされたプロパティーを返す
  }
};

const proxy = new Proxy(targetObj, handler);
proxy.a;           //「aにアクセス。getトラップ発動」が出力

1-3 deletePropertyトラップ

値が削除された時に独自の処理を追加できます。deletePropertyトラップには、targetprop2つの引数を渡します。以下の例では、delete演算子でプロパティーaにの値を削除したときに、console.logが呼ばれます。

const targetObj = { a: 0 };

/**
 * @param target    new Proxy()の第一引数に渡される独自処理を追加したいオブジェクト
 * @param prop      アクセスされたプロパティーの名前
 */
const handler = {
  deleteProperty: function (target, prop) {
    console.log(`${prop}の値を削除。deletePropertyトラップ発動`);
    delete target[prop];  //値を削除
  }
};

const proxy = new Proxy(targetObj, handler);
delete proxy.a;           //「aの値を削除。deletePropertyトラップ発動」が出力

1-4 その他トラップ

その他のトラップは👇をご参照ください。

Proxy - JavaScript | MDN

メタプログラミング - JavaScript | MDN

2 具体的な使用例

2-1 プロパティの更新を許可しない(バリデーションも)

setトラップの中で、プロパティの更新処理を書かず、throw new Errorなどをしてあげると、プロパティの更新を制限できます。

const targetObj = { a: 0 };

const handler = {
  set: function () {
    throw new Error("プロパティの更新はできません")
  }
};

const proxy = new Proxy(targetObj, handler);
proxy.a = 100;           //エラー「プロパティの更新はできません」

同じような考え方で、バリデーションもできます。下の例では、Number型以外でのプロパティの更新を許可しないようにしています。

const targetObj = { a: 0 };

const handler = {
  set: function (target, prop, value) {
    if (typeof value === "number") {
      target[prop] = value;
    } else {
      throw new Error("Number型でなければ更新はできません");
    }
  },
};

const proxy = new Proxy(targetObj, handler);
proxy.a = 100;         //Number型であれば更新できる。
console.log(proxy.a);  //100
proxy.a = "ああああ";   //エラー「Number型でなければ更新はできません」

2-2 値の削除を許可しない

2-1のsetトラップと同様に、deletePropertyトラップの中で、値の削除処理を書かず、throw new Errorなどをしてあげると、値の削除を制限できます。

const targetObj = { a: 0 };

const handler = {
  deleteProperty: function () {
      throw new Error("値の削除はできません")
  }
};

const proxy = new Proxy(targetObj, handler);
delete proxy.a;           //エラー「値の削除はできません」

2-3 デフォルト値を返すようにする

getトラップを利用すると、アクセスされたプロパティーが見つからない場合に、デフォルト値を返すように設定することが可能です。

const targetObj = { a: 0 };

const handler = {
  get: function (target, prop, receiver) {
    if (prop in target) {  //プロパティーのチェック
      return target[prop];
    } else {
      return "hoge";        //プロパティーが見つからなければ、デフォルト値を返す
    }
  },
};

const proxy = new Proxy(targetObj, handler);
console.log(proxy.a);
console.log(proxy.b);

最後に

Vue.jsのソースコードなどでよく見るProxyオブジェクトに関して、整理しました。オブジェクトの操作を拡張してくれる非常に強力なツールですね。

後半の記事(👇)では、Reflectオブジェクトに関してProxyオブジェクトとの併用方法なども含めて整理したいと思います!
https://zenn.dev/oreo2990/articles/a28abc47bc22b5

参考

Proxy と Reflect

【ES6】Proxyオブジェクトについて - Qiita

Proxy - JavaScript | MDN

Discussion