vue3 响应式原理笔记
v2响应式原理
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- 对象:其核心是递归
object
的每一个属性,(这也是浪费性能的地方),给每个对象属性增加getter
和setter
,当属性发生变化的时候会更新视图
缺点:defineProperty
只能检测到对象自带的属性,无法检测到对象属性的新增
和 删除
后期
v2
提供了this.$set(target, prop, val)
来确保你新增的对象属性
也具有响应式
官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如this.myObject.newProperty = ‘hi’
)
- 数组:重写了那些会改变原数组的方法,原因是
defineProperty
不能检测到数组长度的变化,准确的说是通过改变length
而增加的长度不能监测到(arr.length = newLength
也不会)。比如重写的:push、pop、shift、unshift、sort、reverse、splice
方法,以便数组发生变化后能够给观察者发送通知,并且push、unshift、splice
会给数组新增元素,我们还需要知道新增的是什么数据,需要对这些新增的数据进行观测。
v3响应式原理
Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。其实就是在对
目标对象
的操作之前提供了拦截
,意味着你可以在这层拦截中进行各种操作,修改某些操作的默认
行为。这样我们可以不直接操作对象本身
,而是通过操作对象的代理对象
来间接来操作对象,来实现数据的响应式。
语法
1 | const p = new Proxy(target, handler) |
参数一:要使用 Proxy
包装的目标target 对象
(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
参数二: handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
v3
使用了 es6
的 Proxy
来实现数据的响应式,Proxy
消除了之前 v2.x
中基于 Object.defineProperty
的实现所存在的限制:无法监听属性的添加和删除、数组索引和长度的变更
不过v3
底层不是直接对target
进行如下的简单操作。而是利用es6
的Reflect
。 Reflect
是一个内置的对象,它提供拦截JavaScript
操作的方法。这些方法与proxy handlers
的方法相同。Reflect
不是一个函数对象,因此它是不可构造的 详情:Reflect
v3响应式示例
以下是一个将 user
对象变为响应式对象的示例
首先定义一个 user
对象,这是我们的目标对象,也就是 Proxy
的第一个参数
1 | type userType = { |
然后再定义一个 handle
对象,这是 Proxy
的第二个参数,这里面包含 get()、set()、deleteProperty()
方法
这里有一个问题,如果这个对象是个多层对象,
Proxy
并不会代理多层对象(只会代理第一层),因为它是懒代理
,与v2.x
的Object.defineProperty
不同Object.defineProperty
方法是一开始就会对这个多层对象进行递归处理,所以可以拿到,所以这里我们需要加一个判断:如果目标对象的属性target[prop]
还是个对象
,则对这个属性对象进行递归代理,这样就能读取到深层对象
1 | const handler: object = { |
最后实例化Proxy
1 | // proxyUser是代理后的对象。当外界每次对 proxyUser 进行操作时,就会执行 handler 对象上的一些方法。 |
测试
获取对象属性
1 | // 获取 name 属性 |
测试结果显示成功获取到了proxyUser.name
属性
1 | // 获取嵌套对象属性 |
测试结果显示成功获取到了proxyUser.realName.firstNamename
属性,为什么执行了两遍?
第一遍执行传的target
是user
,但是过程遇到了以下判断
1 | if (typeof target[prop] === 'object' && target[prop] !== null) { |
以上的意思是:如果target
的属性还是一个object
且不为null
的情况下则进行递归调用取值,此时的target
为target[prop]
,handler
为自身handler
对象,所以才会出现为什么执行了两遍
修改、新增对象属性
1 | console.log(proxyUser.name); // alice |
测试修改嵌套对象属性
1 | console.log(proxyUser.realName); // Proxy {firstName: 'ren', lastName: 'pingsheng'} |
新增一个 gender
属性
1 | proxyUser.gender = 'women'; |
删除对象属性
1 | delete proxyUser.hobby |
测试结果删除成功
以上就是v3
响应式原理
最简单的实现
总结
Proxy
使用上比Object.defineProperty
方便的多Object.defineProperty
只能代理对象上的某个属性,而Proxy
代理整个对象- 如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而
Object.defineProperty
需要在一开始就全部递归,Proxy
性能优于Object.defineProperty
- 对象
新增属性
时,Proxy
可以监听到,Object.defineProperty
监听不到 - 数组
新增删除修改
时,Proxy
可以监听到,Object.defineProperty
监听不到