Proxy Reflect
Proxy
Proxy意思为 “代理”,即在访问对象之前建立一道 “拦截”,任何访问该对象的操作之前都会通过这道 “拦截”,即执行Proxy里面定义的方法。
let pro = new Proxy(target,handler);
/*
new Proxy()表示生成一个Proxy实例
target参数表示所要拦截的目标对象
handler参数也是一个对象,用来定制拦截行为。
*/
handler
Proxy 支持 13 种拦截行为(handle),针对解决上一节的问题,简单介绍下其中 2 种拦截行为,get 与 set。
get
get(target, propKey, receiver)
/*
用于拦截某个属性的读取操作,可以接受三个参数:
- target:目标对象
- propKey:属性名
- receiver(可选):proxy 实例本身(严格地说,是操作行为所针对的对象)
*/
set
set(target, propKey, value, receiver)
/*
用于拦截某个属性的赋值操作,可以接受四个参数:
- target:目标对象
- propKey:属性名
- value:属性值
- receiver(可选):Proxy 实例本身
*/
示例1:
let hero = {
name: '赵云',
age: 25
}
let handler = {}; // 未对拦截对象设定拦截方法
let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name); // '赵云'
heroProxy.name = '黄忠';
console.log(heroProxy.name); // '黄忠'
示例2:
let hero = {
name: '赵云',
age: 25
}
let handler = {
get: (hero, name) => {
const heroName = `英雄名是${hero.name}`;
return heroName;
},
set: (hero, name, value) => {
console.log(`${hero.name} change to ${value}`);
hero[name] = value;
return true;
}
};
let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name); // 英雄名是赵云
heroProxy.name = '吕布';
console.log(heroProxy.name);
/*
英雄名是赵云
赵云 change to 吕布
英雄名是吕布
*/
示例3:
Proxy 也可以作为其他对象的原型对象使用。
let hero = {
name: '赵云',
age: 25
}
let handler = {
get: (hero, name) => {
const heroName = `英雄名是${hero.name}`;
return heroName;
},
set: (hero, name, value) => {
console.log(`${hero.name} change to ${value}`);
hero[name] = value;
return true;
}
};
let heroProxy = new Proxy(hero, handler);
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
let obj = Object.create(heroProxy)
console.log(obj.name);
obj.name = '黄忠';
console.log(obj.name);
/*
英雄名是赵云
赵云 change to 吕布
英雄名是吕布
*/
解决数据绑定的问题
let hero = {
name: '赵云',
hp: 100,
sp: 100,
equipment: ['马', '长枪']
}
let handler = {
set(target, property, value) {
console.log(target); // {name: "赵云", hp: 100, sp: 100, equipment: Array(2)}
console.log(property); // hp
console.log(value); // 200
target[property] = value; // 更改属性值
return true;
}
}
let heroProxy = new Proxy(hero, handler);
heroProxy.hp = 200;
console.log(hero.hp); // 200
最后,同样把 “佩剑” 添加到英雄的装备中
let heroProxy2 = new Proxy(hero.equipment, handler);
heroProxy2.push('佩剑');
/*
可以发现,heroProxy.push('佩剑'); 触发了两次 set,原因是push即修改了 hero.equipment 的内容,又修改了 hero.equipment 的 length。
*/
Reflect
在了解了 Proxy
之 后,细心的我们一定发现,若需要在 Proxy
内部调用对象的默认行为,该如何实现?
Reflect
正是 ES6
为了操作对象而提供的新 API
。
基本特点
-
只要
Proxy
对象具有的代理方法,Reflect
对象全部具有,以静态方法的形式存在。这些方法能够执行默认行为,无论Proxy
怎么修改默认行为,总是可以通过Reflect
对应的方法获取默认行为。 -
修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 -
让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
静态方法
Reflect
对象一共有 13 个静态方法(匹配 Proxy
的13种拦截行为)。
Reflect.apply(target, thisArg, args)
Reflect.construct(target, args)
Reflect.get(target, name, receiver)
Reflect.set(target, name, value, receiver)
Reflect.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name)
Reflect.has(target, name)
Reflect.ownKeys(target)
Reflect.isExtensible(target)
Reflect.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
Reflect.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
大部分与 Object
对象的同名方法的作用都是相同的,而且它与 Proxy
对象的方法是一一对应的。
下面通过 3 个实例来对比 Object
对象方法与 Reflect
对象方法。
示例1:
// Object对象方法
try {
Object.defineProperty(target, name, property)
} catch (e) {
console.log('error');
}
//Reflect对象方法
if (Reflect(target, name, property)) {
console.log("success");
} else {
console.log("error")
}
// 由于 Reflect(target, name, property) 返回的是 boolean,代码语义性更好。
示例2:
let hero = {
name: '赵云',
hp: 100,
sp: 100,
equipment: ['马', '长枪']
}
//Object对象方法
console.log('name' in hero); // true
//Reflect对象方法
console.log(Reflect.has(hero, 'name')); // true
// Object操作是命令式,而Reflect让它们变成了函数行为
示例3:
let hero = {
name: '赵云',
hp: 100,
sp: 100,
equipment: ['马', '长枪']
}
let handler = {
get(target, name, receiver) {
if (name === "name") {
console.log("success");
} else {
console.log("failure");
}
return Reflect.get(target, name, receiver);
}
}
let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name);
/*
Reflect 对象的操作和 Proxy 对象的操作一一对应,在 Proxy 的拦截操作中,可以直接利用 Reflect 对象直接获取 Proxy 的默认值。
*/
/*
success
赵云
*/
观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦数据有变化,函数就会自动执行。
let hero = {
name: '赵云',
hp: 100,
sp: 100,
equipment: ['马', '长枪']
}
const handler = {
set(target, key, value, receiver) {
//内部调用对应的 Reflect 方法
const result = Reflect.set(target, key, value, receiver);
console.log(result);
// Reflect.set() 已经更改了 hero 的值
console.log(hero); // {name: "黄忠", hp: 100, sp: 100, equipment: Array(2)}
//执行观察者队列
observableArray.forEach(item => item());
return result;
}
}
//初始化Proxy对象,设置拦截操作
const createProxy = (obj) => new Proxy(obj, handler);
//初始化观察者队列
const observableArray = new Set();
const heroProxy = createProxy(hero);
//将监听函数加入队列
observableArray.add(() => {
console.log(heroProxy.name);
});
heroProxy.name = "黄忠";
// --> 黄忠
对象的多重继承
实现对象间的单继承,比如 obj2
继承 obj1
,可以使用 Object.setPrototypeOf
方法,但是没法实现多继承。
const people = {
name: 'people',
run() {
console.log('people.run:', this.name);
}
};
const powerMan = {
name: 'powerMan',
run() {
console.log('powerMan.run:', this.name);
},
fight() {
console.log('powerMan.fight:', this.name);
}
};
const handler = {
get(target, name, receiver) {
if (Reflect.has(target, name)) {
return Reflect.get(target, name, receiver);
} else {
for (let P of target[Symbol.for('[[Prototype]]')]) {
if (Reflect.has(P, name)) {
return Reflect.get(P, name, receiver);
}
}
}
}
};
const hero = new Proxy({
name: 'hero',
strike() {
this.run();
this.fight();
}
}, handler);
hero[Symbol.for('[[Prototype]]')] = [people, powerMan];
hero.strike();
/*
people.run: hero
powerMan.fight: hero
*/
用了一个自定义的属性 Symbol.for("[[Prototype]]")
来表示要继承的多个父对象。
然后用 Proxy
来拦截所有 hero
中的 get
请求,使用 Reflect.has
方法检查 hero
中是否存在相应的属性或者方法。
如果存在,则直接转发;如果不存在,则遍历父对象列表,在父对象中逐个检查是否存在相应的属性或者方法。
若存在则调用。若不存在,则相当于 get
返回 undefined
。