ES6 中新增了一个 Proxy 类,Proxy 的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器。可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
通过
Object.defineProperty()
也可以实现监听对象属性的操作 。let person = {name: 'Lee'} let _name = person.name Object.defineProperty(person, 'name', { get: function() { console.log('监听获取值') return _name }, set: function(newVal) { console.log('监听设置值') _name = newVal } }) console.log(person.name) person.name = 'Mary'
但是是存在弊端的:首先,Object.defineProperty()
设计的初衷并不是为了去监听一个完整对象中所有的属性;其次,如果想监听更加丰富的操作,比如新增属性、删除属性、是否存在属性等,是无法做到的。
Vue2 就是通过
Object.defineProperty()
来实现响应式原理的,但在 Vue3 中改为了通过 Proxy 来实现响应式原理。
创建 Proxy 实例:
如果想要监听一个对象的相关操作,可以先通过 new Proxy()
来创建一个代理对象,之后对目标对象的所有操作,都通过代理对象来完成,代理对象可以监听对目标对象对象进行了哪些操作,并自动反馈给目标对象。
new Proxy()
接收两个参数,第一个参数表示所要拦截的目标对象;第二个参数是一个配置对象,用来定制代理行为,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
var target = {};
var handler = {
get: function(target, propKey) {
return 35
}
}
// 创建一个代理对象
var proxy = new Proxy(target,handler)
// 对目标对象的所有操作,都要通过代理对象来进行,代理对象会自动反馈给目标对象
proxy.name // 35
proxy.age // 35
如果配置对象是一个空对象,没有设置任何拦截,那就等同于直接通向源对象,访问代理对象就等同于访问目标对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler)
proxy.name = 'Lee'
target.name // Lee
Proxy.revocable():
Proxy.revocable()方法返回一个可取消的Proxy实例。该方法返回一个对象,该对象的proxy属性是proxy实例,revoke属性是一个函数,可以取消Proxy实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this问题:
虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。
主要原因就是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy而不是tarhet。
捕获器:
如果想要监听某些具体的操作,可以在配置对象中添加对应的捕获器。
get()
:属性读取操作的捕获器。用来监听读取属性的操作,默认会接收到三个参数,依次为目标对象,被获取的属性名 key 和代理对象。let person = {name: 'Lee'} let proxy = new Proxy(person, { // 读取属性时自动执行 get: function(target, key, receiver){ return target[key] } }) proxy.name //Lee
set()
:属性设置操作的捕获器。用来监听设置属性的操作,默认会接收到四个参数,依次为目标对象、被设置属性名 key、被设置的属性值和代理对象。let person = {name: 'Lee'} let proxy = new Proxy(person, { get: function(target, key, receiver){ return target[key] }, // 设置属性时自动执行 set: function(target, key, value, receiver){ target[key] = value }, }) proxy.name = 'Mary' proxy.name // Mary
deleteProperty()
:delete 操作符的捕获器。用来监听删除属性的操作。let person = { name: 'Lee' } let proxy = new Proxy(person, { // 删除属性时自动执行 deleteProperty (target, key) { delete target[key] } }) delete proxy.name
has()
:in 操作符的捕获器。用来监听判断属性是否存在的操作。apply()
:函数调用操作的捕获器。用来监听函数的调用。construct()
:new 操作符的捕获器。用来监听 new 操作。ownKeys()
:Object.getOwnPropertyNames()
和Object.getOwnPropertySymbols()
方法的捕获器。defineProperty()
:Object.defineProperty()
方法的捕获器。getOwnPropertyDescriptor()
:Object.getOwnPropertyDescriptor()
方法的捕获器。返回值是一个属性描述对象或者 undefined。getPropertyOf()
:Object.getPropertyOf()
方法的捕获器。用来监听获取对象的隐式原型。返回值必须是对象或者null,否则报错。setPropertyOf()
:Object.setPropertyOf()
方法的捕获器。用来监听获取对象的隐式原型。返回值必须是布尔值,否则会被自动转为布尔值。isExtensible()
:Object.isExtensible()
方法的捕获器。返回值只能是布尔值,否则会被自动转为布尔值。preventExtensions()
:Object.preventExtensions()
方法的捕获器。返回值只能是布尔值,否则会被自动转为布尔值。