概要
Proxy と Reflect は、ES6 がオブジェクトを操作するために導入した API です。
Proxy は、ターゲットオブジェクトの読み取りや関数呼び出しなどの操作をインターセプトし、その後操作処理を行うことができます。オブジェクトを直接操作するのではなく、プロキシパターンのように、オブジェクトのプロキシオブジェクトを介して操作を行います。これらの操作を行う際に、必要な追加操作を加えることができます。
Reflect はターゲットオブジェクトの振る舞いを取得するために使用できます。Object に似ていますが、より読みやすく、オブジェクト操作に、よりエレガントな方法を提供します。そのメソッドは Proxy と対応しています。
基本的な使い方
Proxy
1つの Proxy オブジェクトは、target と handler の2つの部分から構成されます。Proxy コンストラクタを介してインスタンスオブジェクトを生成する際には、この2つのパラメータを提供する必要があります。target はターゲットオブジェクトであり、handler は、ターゲットの指定された振る舞いを代理することを宣言するオブジェクトです。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key];
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name
proxy.age = 25
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
proxyEpt.name
proxyEpt.name = 'Tom'
proxyEpt.name
targetEpt
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty
インスタンスメソッド
get(target, propKey, receiver)
target オブジェクト上での propKey の読み取り操作に使用されます。
let exam ={
name: "Tom",
age: 24
}
let proxy = new Proxy(exam, {
get(target, propKey, receiver) {
console.log('Getting ' + propKey);
return target[propKey];
}
})
proxy.name
get() メソッドは継承できます。
let proxy = new Proxy({}, {
get(target, propKey, receiver) {
if(propKey[0] === '_'){
throw new Erro(`Invalid attempt to get private "${propKey}"`);
}
console.log('Getting ' + propKey);
return target[propKey];
}
});
let obj = Object.create(proxy);
obj.name
set(target, propKey, value, receiver)
target オブジェクト上での propKey の代入操作をインターセプトするために使用されます。ターゲットオブジェクト自身のあるプロパティが書き込み不可かつ設定変更不可である場合、set メソッドは機能しません。
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
obj[prop] = value;
}
};
let proxy= new Proxy({}, validator)
proxy.age = 100;
proxy.age
proxy.age = 'oppps'
proxy.age = 300
4番目のパラメータ receiver は、元の操作の振る舞いが存在するオブジェクトを表し、通常は Proxy インスタンス自体です。
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.name= 'Tom';
proxy.name=== proxy
const exam = {}
Object.setPrototypeOf(exam, proxy)
exam.name = "Tom"
exam.name === exam
注意: 厳格モードでは、set プロキシが true を返さない場合、エラーが発生します。
apply(target, ctx, args)
関数の呼び出し、call および reply 操作をインターセプトするために使用されます。target はターゲットオブジェクト、ctx はターゲットオブジェクトのコンテキスト、args はターゲットオブジェクトの引数配列を表します。
function sub(a, b){
return a - b;
}
let handler = {
apply: function(target, ctx, args){
console.log('handle apply');
return Reflect.apply(...arguments);
}
}
let proxy = new Proxy(sub, handler)
proxy(2, 1)
has(target, propKey)
HasProperty 操作をインターセプトするために使用されます。つまり、target オブジェクトに propKey プロパティが存在するかどうかを判断する際に、このメソッドによってインターセプトされます。このメソッドは、プロパティがオブジェクト自身のプロパティであるか、継承されたプロパティであるかは判断しません。
let handler = {
has: function(target, propKey){
console.log("handle has");
return propKey in target;
}
}
let exam = {name: "Tom"}
let proxy = new Proxy(exam, handler)
'name' in proxy
注意: このメソッドは for...in ループをインターセプトしません。
construct(target, args)
new コマンドをインターセプトするために使用されます。戻り値はオブジェクトでなければなりません。
let handler = {
construct: function (target, args, newTarget) {
console.log('handle construct')
return Reflect.construct(target, args, newTarget)
}
}
class Exam {
constructor (name) {
this.name = name
}
}
let ExamProxy = new Proxy(Exam, handler)
let proxyObj = new ExamProxy('Tom')
console.log(proxyObj)
deleteProperty(target, propKey)
delete 操作をインターセプトするために使用されます。このメソッドがエラーをスローするか false を返すと、propKey プロパティは delete コマンドで削除できません。
defineProperty(target, propKey, propDesc)
Object.definePro をインターセプトするために使用されます。ターゲットオブジェクトが拡張不可の場合、ターゲットオブジェクトに存在しない属性を追加するとエラーになります。属性が書き込み不可または設定変更不可の場合、これらの属性を変更することはできません。
let handler = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
return true;
}
}
let target = {}
let proxy = new Proxy(target, handler)
proxy.name = "Tom"
target
let handler1 = {
defineProperty: function(target, propKey, propDesc){
console.log("handle defineProperty");
return false;
}
}
let target1 = {}
let proxy1 = new Proxy(target1, handler1)
proxy1.name = "Jerry"
target1
erty 操作
getOwnPropertyDescriptor(target, propKey)
Object.getOwnPropertyD() をインターセプトするために使用されます。戻り値はプロパティ記述オブジェクトまたは undefined です。
let handler = {
getOwnPropertyDescriptor: function(target, propKey){
return Object.getOwnPropertyDescriptor(target, propKey);
}
}
let target = {name: "Tom"}
let proxy = new Proxy(target, handler)
Object.getOwnPropertyDescriptor(proxy, 'name')
ptor 属性
getPrototypeOf(target)
主にオブジェクトのプロトタイプを取得する操作をインターセプトするために使用されます。以下の操作が含まれます:
- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
let exam = {}
let proxy = new Proxy({},{
getPrototypeOf: function(target){
return exam;
}
})
Object.getPrototypeOf(proxy)
注意: 戻り値はオブジェクトまたは null でなければなりません。そうでない場合はエラーが発生します。また、ターゲットオブジェクトが拡張不可(non-extensible)の場合、getPrototypeOf メソッドはターゲットオブジェクトのプロトタイプオブジェクトを返さなければなりません。
let proxy = new Proxy({},{
getPrototypeOf: function(target){
return true;
}
})
Object.getPrototypeOf(proxy)
isExtensible(target)
Object.isExtensible 操作をインターセプトするために使用されます。
このメソッドはブール値のみを返すことができます。そうでない場合、戻り値は自動的にブール値に変換されます。
let proxy = new Proxy({},{
isExtensible:function(target){
return true;
}
})
Object.isExtensible(proxy)
注意: その戻り値はターゲットオブジェクトの isExtensible プロパティと一致している必要があります。そうでない場合、エラーがスローされます。
let proxy = new Proxy({},{
isExtensible:function(target){
return false;
}
})
Object.isExtensible(proxy)
ownKeys(target)
オブジェクト自身のプロパティの読み取り操作をインターセプトするために使用されます。主に以下の操作が含まれます:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- or...in
メソッドが返す配列のメンバーは、文字列または Symbol 値のみでなければなりません。そうでない場合、エラーが発生します。
ターゲットオブジェクトに設定変更不可のプロパティが含まれている場合、これらのプロパティを結果に返す必要があります。そうでない場合はエラーが発生します。
ターゲットオブジェクトが拡張不可の場合、すべてを返さなければならず、ターゲットオブジェクトが含むすべてのプロパティのみを返すことができ、存在しないプロパティを含めることはできません。そうでない場合もエラーが発生します。
let proxy = new Proxy( {
name: "Tom",
age: 24
}, {
ownKeys(target) {
return ['name'];
}
});
Object.keys(proxy)
let target = {
name: "Tom",
[Symbol.for('age')]: 24,
};
Object.defineProperty(target, 'gender', {
enumerable: false,
configurable: true,
writable: true,
value: 'male'
});
let handler = {
ownKeys(target) {
return ['name', 'parent', Symbol.for('age'), 'gender'];
}
};
let proxy = new Proxy(target, handler);
Object.keys(proxy)
preventExtensions(target)
Object.preventExtensions 操作をインターセプトします。
このメソッドはブール値を返さなければなりません。そうでない場合、自動的にブール値に変換されます。
var proxy = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});
Object.preventExtensions(proxy)される
var proxy = new Proxy({}, {
preventExtensions: function(target) {
Object.preventExtensions(target);
return true;
}
});
Object.preventExtensions(proxy)
setPrototypeOf
主に Object.setPrototypeOf メソッドをインターセプトするために使用されます。
戻り値はブール値でなければなりません。そうでない場合、自動的にブール値に変換されます。
ターゲットオブジェクトが拡張不可の場合、setPrototypeOf メソッドはターゲットオブジェクトのプロトタイプを変更してはなりません。
let proto = {}
let proxy = new Proxy(function () {}, {
setPrototypeOf: function(target, proto) {
console.log("setPrototypeOf");
return true;
}
}
);
Object.setPrototypeOf(proxy, proto);
Proxy.revocable()
キャンセル可能な Proxy インスタンスを返すために使用されます。
let {proxy, revoke} = Proxy.revocable({}, {});
proxy.name = "Tom";
revoke();
proxy.name
Reflect
ES6では、Objectの明らかに言語内部に属するいくつかのメソッドがReflectオブジェクトに移植されました(現在、一部のメソッドはObjectとReflectの両方に存在します)。将来の新しいメソッドはReflectオブジェクトにのみ配置されます。
Reflect オブジェクトは、一部のメソッドの戻り値を変更して、より合理的にしています。
Reflect オブジェクトは、関数方式で Object の命令的操作を実装しています。
静的メソッド
Reflect.get(target, name, receiver)
target オブジェクトの name プロパティを検索して返します。
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name');
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver);
Reflect.get(exam, 'birth');
Reflect.get(1, 'name');
Reflect.set(target, name, value, receiver)
target の name プロパティを value に設定します。戻り値は boolean で、true は変更成功を表し、false は失敗を表します。target がオブジェクトでない場合、エラーが発生します。
let exam = {
name: "Tom",
age: 24,
set info(value){
return this.age = value;
}
}
exam.age;
Reflect.set(exam, 'age', 25);
exam.age;
Reflect.set(exam, 'age', );
exam.age;
let receiver = {
age: 18
}
Reflect.set(exam, 'info', 1, receiver);
receiver.age;
let receiver1 = {
name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age;
Reflect.has(obj, name)
name in obj 命令を関数化したもので、name プロパティが obj オブジェクト内に存在するかどうかを調べるために使用されます。戻り値は boolean です。obj がオブジェクトでない場合は TypeError が発生します。
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name');
Reflect.deleteProperty(obj, property)
delete obj[property] を関数化したもので、obj オブジェクトの property プロパティを削除するために使用されます。戻り値は boolean です。obj がオブジェクトでない場合は TypeError が発生します。
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name');
exam
Reflect.deleteProperty(exam , 'name');
Reflect.construct(obj, args)
new target(...args) と同等です。
function exam(name){
this.name = name;
}
Reflect.construct(exam, ['Tom']);
Reflect.getPrototypeOf(obj)
obj の _proto_ プロパティを読み取るために使用されます。obj がオブジェクトでない場合、Object のように obj をオブジェクトに変換するのではなく、エラーが発生します。
class Exam{}
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype
Reflect.setPrototypeOf(obj, newProto)
ターゲットオブジェクトの prototype を設定するために使用されます。
let obj ={}
Reflect.setPrototypeOf(obj, Array.prototype);
Reflect.apply(func, thisArg, args)
Function.prototype.apply.call(func, thisArg, args) と同等です。func はターゲット関数を表します。thisArg はターゲット関数にバインドされる this オブジェクトを表します。args はターゲット関数呼び出し時に渡される引数リストで、配列または配列風オブジェクトです。ターゲット関数が呼び出し可能でない場合、TypeError がスローされます。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]);
Reflect.defineProperty(target, propertyKey, attributes)
対象オブジェクトにプロパティを定義するために使用します。target がオブジェクトでない場合、エラーがスローされます。
let myDate= {}
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
const student = {};
Reflect.defineProperty(student, "name", {value: "Mike"});
student.name;
Reflect.getOwnPropertyDescriptor(target, propertyKey)
target オブジェクトの propertyKey プロパティの記述子を取得するために使用します。target がオブジェクトでない場合、引数が不正であることを示すエラーがスローされ、非オブジェクトをオブジェクトに変換しません。
var exam = {}
Reflect.defineProperty(exam, 'name', {
value: true,
enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
Reflect.getOwnPropertyDescriptor(exam, 'age')
Reflect.isExtensible(target)
target オブジェクトが拡張可能かどうかを判定するために使用します。戻り値は boolean です。target 引数がオブジェクトでない場合、エラーがスローされます。
let exam = {}
Reflect.isExtensible(exam)
Reflect.preventExtensions(target)
target オブジェクトを拡張不可にするために使用します。target 引数がオブジェクトでない場合、エラーがスローされます。
let exam = {}
Reflect.preventExtensions(exam)
Reflect.ownKeys(target)
target オブジェクトのすべてのプロパティを返すために使用します。これは Object.getOwnPropertyNames と Object.getOwnPropertySymbols を合わせたものに相当します。
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam)
組み合わせて使用
Reflect オブジェクトのメソッドと Proxy オブジェクトのメソッドは一対一対応です。したがって、Proxy オブジェクトのメソッドは Reflect オブジェクトのメソッドを呼び出すことでデフォルトの動作を取得し、その後追加の操作を行うことができます。
let exam = {
name: "Tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
使用シーンの拡張
オブザーバーパターンの実装
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}