初识Vue3.0的Proxy
技术、总结 Apr 24, 2020
什么是Proxy
Proxy翻译过来即为代理,当对一个对象进行任何操作/访问的时候,都必须经过这层代理。例如当你想要给一个对象增加一个属性的时候,经过代理的处理可在对象上增加你想要的数据结构。
ES6原生提供Proxy构造函数,Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)——MDN。
语法
const p = new Proxy(target, handler)
target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数、甚至另一个代理)。
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时p的行为。MDN文档
示例
const p = new Proxy({}, {
set(target, propKey, value) {
if (propKey === 'name') {
throw new TypeError('name属性不允许修改');
}
target[propKey] = value;
}
});
p.name = 'proxy'; // TypeError: name属性不允许修改
p.sex = 'male';
console.log(p.sex); // male
为什么是Proxy
Vue2.x Object.defineProperty()
MDN上对Object.defineProperty()的说明,该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
在Vue2.x中,在递归遍历data中的数据时,使用Object.defineProperty()劫持getter和setter,在getter中做数据依赖收集处理,在setter中监听数据的变化,并通知订阅当前数据的地方。部分源码
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
存在的问题
1、检测不到对象属性的添加和删除。当在对象上新加一个属性时,新加的属性并没有加入vue监测数据更新的机制(因为实在初始化之后添加的)。
2、无法监控数组下标的变化,导致直接通过数组下标给数组赋值,不能实时响应。
3、当data中数据比较多且层级很深的时候,会有性能问题,因为要遍历data中所有的数据并给其设置成响应式的。
Vue3.0 使用Proxy
在Vue3.0中通过使用Proxy来代替Object.defineProperty()来解决上述问题,因为当对一个对象进行任何操作/访问的时候,都必须经过这层代理Proxy。
举个例子
使用Object.defineProperty()
class Observer {
constructor(data) {
// 遍历参数data的属性,给添加到this上
for (let key of Object.keys(data)) {
if (typeof data[key] === 'object') {
data[key] = new Observer(data[key]);
}
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
console.log('getter:' + key);
return data[key];
},
set(newVal) {
console.log('setter:' + key + '=' + newVal);
if (newVal === data[key]) {
return;
}
data[key] = newVal;
}
})
}
}
}
const obj = {
name: 'app'
}
const app = new Observer(obj);
app.name = 'appNewName';
console.log(app.name);
app.age = '18';
console.log(app.age);
执行结果
// 修改obj的name属性
setter:name=appNewName
getter:name
appNewName
// 新增属性
18
使用proxy
const obj = {
name: 'proxy',
np: {
a: 1
}
}
const p = new Proxy(obj, {
get(target, propKey, receiver) {
console.log('getter:' + propKey)
return Reflect.get(target, propKey, receiver)
},
set(target, propKey, value, receiver) {
console.log('setter:' + propKey + '=' + value)
Reflect.set(target, propKey, value, receiver)
}
})
p.name = 'proxyNew';
console.log(p.name);
p.age = '18';
console.log(p.age);
执行结果
// 修改原name属性
setter:name=proxyNew
getter:name
proxyNew
// 新增age属性
setter:age=18
getter:age
18
通过对比,可以看出给对象新增一个属性,使用Object.defineProperty并没有监听到,而使用proxy则可以监听到。
总结
1、Proxy用来操作对象,Object.defineProperty()用来操作对象的属性。
2、Vue 2.x 使用Object.defineProperty() 实现数据响应。
3、Vue 3.0 使用Proxy实现数据响应。
注:
[1] Proxy-MDN
[3] 深入响应式原理