Proxy类 & Reflect对象
Proxy 类
- 是一个类,如果我们希望监听一个对象的相关操作,可以先创建一个代理对象(
Proxy对象) - 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们对原对象进行哪些操作
- 可以将代理理解为通用化的
setter与getter,区别是 每个setter与getter仅能控制单个对象属性,而代理可用于对象交互的通 注意 用处理,包括调用对象的方法。
基本用法
Proxy 是一个类,可以传入两个参数 new Proxy(target, handler)
target:需要被代理的对象handler:里面可以自定义捕获器trap,一共有13种(包括get / set)Proxy返回的是一个代理对象,可以通过这个对象访问、配置被代理对象的属性(会经过捕获器的处理)
const obj = {
name: "xmc",
};
const objProxy = new Proxy(obj, {
get(target, key) {
return target[key];
},
set(target, key, newValue) {
target[key] = newValue;
},
});
console.log(objProxy.name); // 'xmc'
objProxy.name = "zs";
console.log(obj.name); // 'zs'
get / set
常用的捕获器就那几个,既然是响应式基础,我们就看看实现响应式的两个主要捕获器
它们都会传入三个参数target / property / receiver,set 会多传一个newValue,用于修改被代理对象的值
target:指向被代理的对象property:需要修改的属性名receiver:用于正确的在捕获器中传递上下文。大多数场景指代理对象本身,如果有实例继承了代理对象并在原型链上调用了这个get,那么就会指向这个子实例。
const obj = {
name: "xmc",
};
const objProxy = new Proxy(obj, {
get(target, property, receiver) {
console.log(target === obj); // true
console.log(receiver === objProxy); // true
},
set(target, property, newValue, receiver) {
console.log(target === obj); // true
console.log(receiver === objProxy); // true
},
});
// 仅使用代理对象
console.log(objProxy.name);
objProxy.name = "aaa";
const obj = {
name: "xmc",
};
const son = {};
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
console.log("objProxy:", receiver === objProxy);
console.log("son:", receiver === son);
},
});
// objProxy: true
// son: false
console.log(objProxy.name);
// 使用子实例
son.__proto__ = objProxy;
// objProxy: false
// son: true
console.log(son.name);
Reflect 对象
Reflect 提供了和 Proxy 的捕获器是一一对应的方法。
这些方法有点像 Object 中操作对象的方法。比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()
我们有Object可以做这些操作,为什么还需要有 Reflect 这样的新增对象呢?
- 在早期ES规范中,没有考虑到对象本身操作如何设计会更加规范,所以就将这些API放到了
Object上面 - 但是由于
Object作为一个构造函数,直接拿来做操作是不合适的 - 另外还包含类似于
in、delete操作符,让JS看起来是会有一些奇怪的
所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect上面
Receiver
Reflect
在捕获器 get / set 中可以传入 receiver 参数,它有什么用呢?
这个参数类似于apply / call 中添加的对象,能够改变 target 内部 this 的指向
来看个例子
const obj = {
_name: "xmc",
get name() {
return this._name;
}
};
const res1 = Reflect.get(obj, "name");
const res2 = Reflect.get(obj, "name", { _name: "zs" });
console.log(res1, res2); // xmc zs
Proxy
在 Proxy 的一些捕获器中,也有 receiver ,但是这里指向的是正确的上下文对象
const obj = {
name: "xmc",
};
const son = {}
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(objProxy === receiver);
}
});
objProxy.name; // true
son.__proto__ = objProxy;
son.name; // false
应用场景
场景一
假如我们在对象中存在外部不能直接访问的属性,但是我们又需要监听对象中所有属性,如下
const obj = {
name: "xmc",
_age: 21,
get age() {
return this._age;
},
};
显然,如果我们在代理的对象捕获器 get 中 return target[key] ,那么this._age 会直接被调用,而不会通过 Proxy 的代理
const obj = {
name: "xmc",
_age: 21,
get age() {
return this._age;
},
};
const objProxy = new Proxy(obj, {
get(target, key) {
console.log(key);
return target[key];
},
});
console.log(objProxy.age); // age 21
这里的 get 只会被调用一次,且 key 是 age
如果我们使用 receiver 又会是什么样呢
const obj = {
name: "xmc",
_age: 21,
get age() {
return this._age;
},
};
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(key);
return Reflect.get(target, key, receiver);
},
});
console.log(objProxy.age); // age _age 21
这里的 get 会被调用两次,obj._age 成功通过代理被访问
场景二
如果有实例继承了代理对象,如果调用的方法来自于代理对象,且方法内部有使用 this,简单的在get 中 return Relect(target, key) ,这样不能将正确的上下文传入。
const obj = {
name: "xmc",
get value() {
return this.name;
},
};
const son = {
name: "zs",
};
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key);
},
});
son.__proto__ = objProxy;
console.log(objProxy.value);// xmc
console.log(son.value); // xmc (希望是zs)
所以需要使用 receiver,即传入当前正确的上下文
const obj = {
name: "xmc",
get value() {
return this.name;
},
};
const son = {
name: "zs",
};
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver); // 传入 receiver
},
});
son.__proto__ = objProxy;
console.log(objProxy.value);// xmc
console.log(son.value); // zs
参考资料
《JavaScript高级程序设计》(第四版)
《JavaScript忍者秘籍》(第二版)