JavaScriptのProxyとReflectについて

JavaScript

JavaScriptのProxyとReflectについて、簡単にまとめました。

Proxyとは

Proxyとは、プロパティの操作に独自の処理を追加するためのオブジェクトのことです。
第一引数にターゲットとなるオブジェクト、第二引数にhandlerという第一引数のオブジェクトを操作するメソッドが格納されているオブジェクトを指定します。
handlerのsetには4つ引数が渡ってきます。Proxyの第一引数のオブジェクト、第二引数はプロパティ、第三引数はsetの場合は可能した新しい値、第4引数はreceiverです。
receiverは、proxyのオブジェクトが渡ってきます。下の例でいうと、proxyObjです。

const targetObj = { prop1: 2 };

const handler = {
  set: function (target, prop, value, receiver) {
    console.log(`set: ${prop}, ${value}`);
    target[prop] = value;
  },
  get: function (target, prop, receiver) {
    console.log(`get: ${prop}`);
    return target[prop];
  },
  deleteProperty: function (target, prop) {
    console.log(`delete: ${prop}`);
    delete target[prop];
  },
};
const proxyObj = new Proxy(targetObj, handler);
// set
proxyObj.prop1 = 5; //set: prop1, 5
console.log(targetObj); //{prop1: 5}

// get
proxyObj.prop1; //get: prop1

// deleteProperty
delete proxyObj.prop1; //delete: prop1
console.log(targetObj); //{} prop1が削除される

トラップしたsetの中でtarget[prop] = valueと値をセットすることで、
targetObjのプロパティの値を変更するまえに、なんらかの処理をすることができます。
値の変更を検知して、独自の処理をすることができます。
getの場合は、値の取得が行われたときの処理を追加できます。
getは新しい値は渡されないので、valueはありません。

deletePropertyで値を削除するときの検知をすることができます。
deletePropertyにはreceiverは渡ってきません。

値の変更を許可しないようにする

一般的なオブジェクトをproxy経由で値を参照することで、独自の操作を追加することができます。

// 値の変更を許可しないようにする
const targetObj = { prop1: 2 };

const handler = {
  set: function (target, prop, value, receiver) {
    console.log(`set: ${prop}, ${value}`);
    target[prop] = value;
    // throw new Error('変更できません。');
  },
  get: function (target, prop, receiver) {
    console.log(`get: ${prop}`);
    return target[prop];
  },
  deleteProperty: function (target, prop) {
    console.log(`delete: ${prop}`);
    // delete target[prop];
    throw new Error('変更できません。');
  },
};
const proxyObj = new Proxy(targetObj, handler);
// set
proxyObj.prop1 = 5; 
// set: prop1, 5 
// Uncaught Error: 変更できません。

// deleteProperty
delete proxyObj.prop1; 
//delete: prop1
// Uncaught Error: 変更できません。
console.log(targetObj);

このように、throwでエラーを設定することで値を新たにsetしたり、getしたり、deleteすることができないように設定することができます。

値が見つからなかった場合のデフォルト値を簡単に設定する

また、getでは、値が見つからなかった場合のデフォルト値を簡単に設定することができます。

const targetObj = { prop1: 2 };

const handler = {
  get: function (target, prop, receiver) {
    if(target.hasOwnProperty(prop)) {
      return target[prop];
    } else {
      return '-1';
    }
  },

};
const proxyObj = new Proxy(targetObj, handler);

// get
console.log(proxyObj.prop2);

Reflectとは

Reflectとは、JavaScriptエンジンの内部の汎用的な関数を呼び出すメソッドが格納されているオブジェクトです。
Reflectを使うことで、関数表記で意図的に内部メソッドにアクセスすることができます。
内部のメソッドとは、例えばset, get, delete, constructなどです。
[[Get]]などと二重の角括弧で囲われているメソッドです。
Reflectは内部メソッドを呼び出す関数の格納場所として使ったり、proxyと合わせて使います。

class Object1 {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
}

const instanceObj1 = new Object1(1,2);
console.log(instanceObj1); // Object1 {a: 1, b: 2}

// new演算子の代わりにしたのReflectを使うことができる
const instanceObj2 = Reflect.construct(Object1, [1,2])
console.log(instanceObj2); // Object1 {a: 1, b: 2}

new演算子を用いていたところを、関数表記にできる点はreflectの特徴です。
in演算子はreflectのhasで書き換えることができます。

console.log('b' in instanceObj2); //bは含まれるのでtrue
console.log(Reflect.has(instanceObj2, 'b')); //bは含まれるのでtrue

また、静的メソッドをreflectに移植することもできます。

Object.defineProperty
Reflect.defineProperty

ほぼ同じでReflectではtry-chatchを使う必要がなく、true/falseで使えます。