背景
终于到年末了,拿出那篇从年初看到年末的响应式原理,拍拍灰尘,叹一口气,终于有时间来复(预)习了!
Vue 的Object.defineProperty和Proxy
题外话
这里肯定很多同学要插嘴了,别人写了那么多,你凭什么来凑热闹?
温故而知新嘛,说不定这里有不知道的呢?(弱小、可怜、又无助)
基本原理
众所周知,响应式的基本原理很简单,Vue2使用了Object.defineProperty
,Vue3使用了Proxy
对象。
那好奇的小明要问了:这两个是啥?为什么它们能够实现响应式?
Object.defineProperty
在伟大的MDN中有这样一段话,介绍了defineProperty
函数的关键作用:
同时他的使用也极为简单,通过传递一个对象,一个对象中属性的key,就可以让这个属性存在响应式被监听。
好奇的狗子来问了:什么被监听?什么响应式?我不明白!
举个栗子
...
<span>{{ name }}</span>
...
data() {
return {
name: 'xxx'
};
}
...
在Vue对象中data返回的对象就是defineProperty
中第一个参数,name是第二个参数,由此vue实现了最基础的响应式原理。一旦name被改变了,defineProperty
中的set函数就被触发,由此才能进入下一步依赖触发。
Proxy
在Vue3.x中,尤大改进了Vue2的响应式痛点,例如:复杂对象内部的增删改监听不到。
这时,好奇的小张要问了:为什么在Vue2中数组和对象中的变动无法做到响应式?
因为defineProperty
中只有get和set可以使用,没有create和delete!所以数组内部的操作还有对象中的操作其实是无法监听到的。
由此,尤大在多年前找浏览器API时发现了Proxy(我猜的)。
它的使用也比较简单,只需要传一个需要代理的对象和一个通常以函数作为属性的对象,据伟大的MDN上记载:
Proxy的set、get和deleteProperty函数
那Proxy是如何比Object.defineProperty
更强的呢?这就要聊到它在Vue3中的应用,set支持监听对象属性的新增和修改,get支持监听对象属性的调用,所以依赖收集和依赖触发都能正常使用并且比Vue2更好用。
举个set的栗子:
let handler = {
set: function(obj, prop, value) {
console.log(`Property ${prop} is set to ${value}`);
obj[prop] = value;
return true;
}
};
let p = new Proxy({}, handler);
p.newProp = 123; // 输出:Property newProp is set to 123
deleteProperty函数我们直接看源码(2024-2),我加了一些注释方便解读:
deleteProperty(target: object, key: string | symbol): boolean {
// 检查目标对象是否有这个属性
const hadKey = hasOwn(target, key)
// 获取旧值
const oldValue = (target as any)[key]
// 使用Reflect API删除属性,并获取操作是否成功的结果
const result = Reflect.deleteProperty(target, key)
// 如果属性删除成功并且目标对象原本就有这个属性
if (result && hadKey) {
// 触发响应系统的更新
// target - 被操作的响应式对象
// TriggerOpTypes.DELETE - 定义了需要触发效果的操作类型,在这里是删除操作
// key - 被删除的属性名,用于定位目标对象中的特定响应式属性
// undefined - 新值,在这里是undefined
// oldValue - 被删除的属性的旧值
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
// 返回操作是否成功的结果
return result
}
通过源码来看,其实理解起来还是非常简单的,核心就是找到属性,删除属性,调用trigger
函数,开始触发之前收集的依赖。
至于Reflect
的故事,等我写完再补。
第一节总结
我们大概聊了一下Vue的两个主要实现,口也渴了,后面的慢慢讲,放假咯~。
内容如有不妥,欢迎指正。
课后问题
为什么Vue2和Vue3删除属性都用的Object.defineProperty
,Vue2咋监听不到?