Vue3.0中,Even You表示会使用
Proxy
代替Object.defineProperty
来做数据的响应式。对于Object.defineProperty
我们已经很熟悉了,之前也写过相关的Vue双向绑定原理(二)访问器属性defineProperty()和发布/订阅模式。
我们也知道了使用Object.defineProperty
的一些劣势:
Object.defineProperty
监听的是对象的属性,如果对象比较复杂,需要逐个深层遍历他的属性来实现监听,耗费性能Object.defineProperty
无法监听数组的变化,使Vue不得不对数组做了额外的hack。相比之下Proxy
就更强大,接下来我们就来了解他。
参考资料:
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。使用方法如下:
// 语法
let p = new Proxy(target, handler);
// 用例
let p = {a: 1};
let proxyP = new Proxy(p, {
get() {
// 获取proxyP对象属性时的自定义逻辑
},
set() {
// 设置proxyP对象属性时的自定义逻辑
}
})
上边的代码中:
这里重点说一下
handler
:handler
本身就是ES6所新设计的一个对象.它的作用就是用来自定义代理对象的各种可代理操作。它本身一共有13中方法,每种方法都可以代理一种操作,常用的几种方法如下:
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
handler.defineProperty()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()
Proxy对于代理模式Proxy的作用主要体现在三个方面:
上边已经说过了Object.defineProperty
的劣势。相应的Proxy
的优势就很明显了:
Proxy的劣势: 兼容性问题,而且无法用polyfill磨平,因此Vue要到3.0版本才能用Proxy重写。
简单例子:
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造(即不可
new Reflect
)的。
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
也就是说,Reflect.fn表示handler中的fn的默认行为。
这里我们看两段代码:
// 这里是get/set方法打印log之后,再执行默认行为
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
// 在浏览器console中,get方法会默认打印出值
// 如果没有Reflect.get执行默认行为,就无法正确打印出值,而会打印undefined
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
// 这里是先执行默认的set确保默认行为执行,set成功之后在打印log,然后返回
var obj = new Proxy({}, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});