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监听不到