【後半】ProxyオブジェクトとReflectオブジェクト
どうもフロントエンドエンジニアのoreoです。
前回の記事(👇)では、Proxy
オブジェクトに関して整理を行いました。今回の記事では、Reflect
オブジェクトに関して、Proxy
オブジェクトとの併用方法なども含めて整理したいと思います!
1 Reflectオブジェクトとは
Reflectオブジェクトは、JavaScriptエンジン内部メソッドを呼び出すメソッドが格納されているオブジェクトです。
JavaScriptエンジンでは、その内部でのみ使用する内部メソッドを保持しており、Reflectオブジェクトを使うことで、それらの内部メソッドに関数表記でアクセスすることが可能となります。
【内部メソッド、Reflect、オブジェクト操作の対応表】
内部メソッド | Reflect呼び出し | オブジェクトの操作 |
---|---|---|
[[Get]] | Reflect.get(obj, prop) | obj[prop] |
[[Set]] | Reflect.set(obj, prop, value) | obj[prop] = value |
[[Delete]] | Reflect.deleteProperty(obj, prop) | delete obj[prop] |
[[Construct]] | Reflect.construct(F, value) | new F(value) |
[[HasProperty]] | Reflect.hsa(obj, value) | prop in obj |
参考 https://ja.javascript.info/proxy#ref-7
2 Reflectオブジェクトのメソッド
get
メソッド
2-1 get
メソッドを使用することで、オブジェクトのゲッターへのアクセスを関数形式で行うことができます。
/**
* @param target 対象オブジェクト
* @param propertyKey アクセスするtargetのプロパティのキー
* @param receiver target[propertyKey]がゲッターの場合、ゲッターのthisが、receiverとして渡したオブジェクトに束縛される
*/
Reflect.get(target,propertyKey,receiver)
以下のような場合、dog.bowWow;
で、ゲッターにアクセスすることができます。
この処理は、JavaScriptエンジン内部では[[Get]]
を実行しています。[[Get]]
はReflect
オブジェクトのget
メソッドで呼び出すことが可能なので、Reflect.get(dog,"bowWow")
で、dog.bowWow
同様の処理を実行可能です。
const dog = {
name: "犬",
get bowWow() {
console.log(`${this.name}が吠えた`);
return this.name;
},
};
dog.bowWow; //「犬が吠えた」が出力
Reflect.get(dog,"bowWow") //「犬が吠えた」が出力
また、get
メソッドの第三引数にオブジェクトを渡すと、ゲッターのthis
を渡したオブジェクトに束縛することが可能です。
const dog = {
name: "犬",
get bowWow() {
console.log(`${this.name}が吠えた`);
return this.name;
},
};
const cat = {
name: "猫",
};
Reflect.get(dog,"bowWow") //「犬が吠えた」が出力。
Reflect.get(dog,"bowWow",cat) //「猫が吠えた」が出力。thisがcatオブジェクトを参照。
set
メソッド
2-2 set
メソッドを使用することで、オブジェクトのセッターを用いた値の設定を関数形式で行うことができます。
/**
* @param target 対象オブジェクト
* @param propertyKey 設定したいtargetのプロパティのキー
* @param value 設定するtargetのプロパティの値
* @param receiver target[propertyKey]がセッターの場合、セッターのthisが、receiverとして渡したオブジェクトに束縛される
*/
Reflect.set(target, propertyKey, value, receiver)
以下のような場合、dog.named = "イギー";
で、セッターを用いて値の設定を行うことができます。
この処理は、JavaScriptエンジン内部では[[Set]]
を実行しています。[[Set]]
はReflect
オブジェクトのset
メソッドで呼び出すことが可能なので、Reflect.set(dog, "name", '承太郎');
で、値の設定が可能です。
const dog = {
name: "犬",
set named(val) {
this.name = val;
},
};
console.log(dog.name); //「犬」が出力
dog.named = "イギー";
console.log(dog.name); //「イギー」が出力
Reflect.set(dog, "name", '承太郎');
console.log(dog.name); //「承太郎」が出力
また、set
メソッドの第四引数にオブジェクトを渡すと、セッターのthis
を渡したオブジェクトに束縛することが可能です。
const dog = {
name: "犬",
set named(val) {
this.name = val;
},
};
const cat = {
name: "猫",
};
console.log(dog.name); //「犬」が出力。
console.log(cat.name); //「猫」が出力。
Reflect.set(dog, "name", "イギー", cat);
console.log(dog.name); //「犬」が出力。
console.log(cat.name); //「イギー」が出力。thisがcatオブジェクトを参照。
construct
メソッド
2-3 construct
メソッドを使用することで、new
演算子と同様の処理を行うことが可能です。
/**
* @param target コンストラクター関数
* @param argumentsList target(コンストラクター関数)に渡す引数の配列
*/
Reflect.construct(target,argumentsList)
例えば、下記のようにAnimals
クラスをnew
演算子でインスタンス化する際、JavaScriptエンジンでは[[Construct]]
を実行しています。
[[Construct]]
は、Reflect
オブジェクトのconstruct
メソッドで呼び出すことが可能なので、Reflect.construct(Animals, ["dog", "cat"])
で、new
演算子と同様にインスタンス化することができます。
class Animals {
constructor(animal_1, animal_2) {
this.animal_1 = animal_1;
this.animal_2 = animal_2;
}
}
const animals_1 = new Animals("dog", "cat");
console.log("animals_1は", animals_1);
const animals_2 = Reflect.construct(Animals, ["dog", "cat"]);
console.log("animals_2は", animals_2);
コンソールで確認してみると、animals_1
、animals_2
には、Animals
クラスのインスタンスが格納されます。
has
メソッド
2-4 has
メソッドを使用することで、in
演算子と同様の判定を行うことが可能です。
/**
* @param target 対象オブジェクト
* @param propertyKey targetにあるかどうか判定したいプロパティーのキー
*/
Reflect.has(target,propertyKey)
in
演算子では、"dog" in animals
のような形で、animals
オブジェクトに"dog"
プロパティーが存在するかをチェックすることが可能でした。これは、JavaScriptエンジン内部では[[HasProperty]]
を実行しおり、[[HasProperty]]
はReflect
オブジェクトのhas
メソッドで呼び出すことが可能なので、Reflect.has(animals,"dog")
で、in
演算子と同様の判定ができます。
const animals ={
dog:"犬",
cat:"猫"
}
console.log("dog" in animals) //true
console.log("bird" in animals) //false
console.log(Reflect.has(animals,"dog")) //true
console.log(Reflect.has(animals,"bird")) //false
2 ProxyオブジェクトとReflectオブジェクト
内部メソッド、Proxy
、Reflect
は全て対になっています。Proxy
、Reflect
と合わせて使うことで、オブジェクトの拡張がより柔軟に記載できます。
【内部メソッド、Reflect
、Proxy
トラップの対応表】
内部メソッド | Reflect呼び出し | Proxyトラップ |
---|---|---|
[[Get]] | Reflect.get(obj, prop) | get |
[[Set]] | Reflect.set(obj, prop, value) | set |
[[Delete]] | Reflect.deleteProperty(obj, prop) | deleteProperty |
[[Construct]] | Reflect.construct(F, value) | construct |
[[HasProperty]] | Reflect.hsa(obj, value) | has |
ここから前回の記事の2-3で記載した、get
トラップを使ってデフォルト値を返す場合において、Reflect
を用いた記載に書き換えてみます。
まず、Proxy
のみの記載です。
/**
* new Proxyに渡すhandlerに、getトラップを定義。
* アクセスしようとするプロパティーが存在する場合は、その値を返す。
* アクセスしようとするプロパティーが存在しない場合は、"hoge"を返す。
*/
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); //「0」が出力
console.log(proxy.b); //「hoge」が出力。
target[prop]
は、Reflect.get(target,prop)
に置き換えることが可能です。
const targetObj = { a: 0 };
const handler = {
get: function (target, prop, receiver) {
if (prop in target) {
return Reflect.get(target,prop); //Reflectオブジェクトを使用する形に変更
} else {
return "hoge";
}
},
};
const proxy = new Proxy(targetObj, handler);
console.log(proxy.a); //「0」が出力
console.log(proxy.b); //「hoge」が出力。
では、targetObj
にゲッターを追加し、ゲッター経由で値を取得する場合を考えます。この場合は問題ありません。
const targetObj = {
a: 0,
get getVal() {
return this.a; //ゲッターを追加
},
};
const handler = {
get: function (target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop);
} else {
return "hoge";
}
},
};
const proxy = new Proxy(targetObj, handler);
console.log(proxy.getVal); //「0」が出力。ゲッター経由で値を取得。
console.log(proxy.b); //「hoge」が出力。
しかし、ゲッターの返す値をthis.b
変更するとconsole.log(proxy.getVal)
の出力値が、hoge
ではなく、undefined
となります。これは、ゲッターのthis
は、proxyオブジェクトでなくtargetObj
そのものを参照するためです。
const targetObj = {
a: 0,
get getVal() {
return this.b; //this.bに変更
},
};
const handler = {
get: function (target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop);
} else {
return "hoge";
}
},
};
const proxy = new Proxy(targetObj, handler);
console.log(proxy.getVal); //「undefined」が出力。
console.log(proxy.b); //「hoge」が出力。
存在しないプロパティーに対してhoge
を返すには、Reflect.get()
に、get
トラップ内でのreceiver
(proxyオブジェクトそのもの)を渡し、ゲッターのthis
を、proxyオブジェクトに束縛することで実現できます。
const targetObj = {
a: 0,
get getVal() {
return this.b;
},
};
const handler = {
get: function (target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop,receiver); //receiverを渡す
} else {
return "hoge";
}
},
};
const proxy = new Proxy(targetObj, handler);
console.log(proxy.getVal); //「hoge」が出力。
console.log(proxy.b); //「hoge」が出力。
このように、Proxy
オブジェクトとReflect
オブジェクト合わせて使うことで、より高度なオブジェクト操作ができます。
最後に
2回にわけて、Proxy
オブジェクトとReflect
オブジェクトに関して整理しました。両方を併用することで、柔軟なオブジェクトの拡張ができそうですね。
あまり使ったことがないので、日々の実装で意識し、定着させていきたいです!
Discussion