vue2的数据响应式是利用Object.defineProperty()来实现,在set和get中,创建依赖、收集依赖,当数据变化时,Watcher再去通知该数据所依赖的地方,进行数据响应式更新。Object.defineProperty()无法监听到对象上的新增属性和删除属性,因此vue2中,对象新增和删除的属性都需要调用$set方法,从而手动的执行创建依赖、收集依赖...等过程。
而Vue3中使用了ES 6中新提供的Proxy代理,来实现数据响应式,MDN上是这样来定义Proxy的:
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
换句话说,Proxy对象能够包装你的目标对象,并且可以拦截到对该对象进行数据操作。Proxy接受两个参数:
const obj = new Proxy(target, handler)
-
target要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 -
handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理obj的行为。
handler对象常用的方法有:
* `handler.get()` 属性读取操作的捕捉器。
* `handler.set()` 属性设置操作的捕捉器。
* `handler.deleteProperty()` delete 操作符的捕捉器。
* `handler.apply()` 函数调用操作的捕捉器。
* `handler.has()` in 操作符的捕捉器。
详细的Proxy文档介绍,可查阅MDN Proxy
先用Proxy包装一个简单的对象,实现读取、新增、删除属性的拦截:
const obj = {
name: 'curry',
age: 34
}
const handler = {
get(target, key) {
const result = Reflect.get(target, key)
console.log('读取数据', key)
return result
},
set(target, key, val) {
const result = Reflect.set(target, key, val)
console.log('写入数据', key)
return result
},
deleteProperty(target, key, val) {
const result = Reflect.deleteProperty(target, key)
console.log('删除数据', key)
return result
},
}
//用new Proxy()生成一个代理的对象
const proxyObj = new Proxy(obj, handler)
//访问name属性
proxyObj.name //控制台打印:读取数据 name
proxyObj.team = 'GSW' //控制台打印:写入数据 team
delete proxyObj.age //控制台打印:删除数据 age
console.log(proxyObj)
可以看到,我们对于proxyObj的读、写操作,甚至delete都可以拦截到,这是Object.defineProperty()所不能实现的。
拦截到数据读取操作之后,利用数据的变化,来实现页面的更新。接下来就写一个简单的例子:定义一个counter,counter1秒钟增加一次,并且把增加后的值同时显示在页面上。
<div id="app"></div>
const obj = { counter: 1 }
const handler = {
get(target, key) {
const result = Reflect.get(target, key)
console.log('读取数据', key)
return result
},
set(target, key, val) {
const result = Reflect.set(target, key, val)
console.log('写入数据', key)
updateHtml()
return result
},
deleteProperty(target, key, val) {
const result = Reflect.deleteProperty(target, key)
console.log('删除数据', key)
return result
},
}
const proxyObj = new Proxy(obj, handler)
const app = document.getElementById('app');
setInterval(() => {
proxyObj.counter++
}, 1000);
function updateHtml() {
app.innerHTML = obj.counter
}
实现效果:
可以看到counter每次变化之后,都会去执行updateHtml方法,从而实现视图的更新,vue3的响应式基本原理大概就是如此,vue会在数据更新的时候,在updateHtml中进行更多的操作,例如:diff算法比较VNode,从而实现差量更新,节省DOM操作性能等。
文章首发自作者博客:博客地址