vue3采用Proxy重写了响应式系统,用来替换vue2 中的DefineProperty方式,Proxy 相对 DefineProperty 做了哪些改进
除了老生常谈的这些
- 不需要初始化时进行深度遍历
- 可以监听动态属性的添加或删除
- 可以监听到数组的索引和数组
length属性变化
分析对组件更新的影响
因为 DefineProperty 对增删,数组没有或不能依赖收集,为了保证可用($set, push等),vue把依赖收集全部放到了一个变量中,任何一项改动都会引起组件的更新
数组
vue2没有对数组中的每一项做响应式处理,所有的响应都是通过 数组 本身这个对象进行收集
export default {
data() {
return {
arr: [1]
}
}
}
此时arr经过处理为:
源码
export function observe(value){
if (isArray(value)) {
return new Observer(value)
}
}
export class Observer {
constructor( value) {
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (isArray(value)) {
;(value).__proto__ = arrayMethods
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i])
}
}
}
}
const methodsToPatch = ['pop','shift','unshift', ...]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
// ... 省略
ob.dep.notify() // 触发更新
return result
})
})
可以看到所有的响应式收集和触发都是通过__ob__这个属性来操作。这里会造成不必要的更新,任何对数组arr的改动都会影响使用arr的组件(即使只使用了arr[0], arr.push(1)也会造成组件重新渲染)
Vue3可以收集数组索引和length属性变化,可以精确到自己使用的属性,不会有这个问题
// 假设子组件
export default {
props: ['arr']
}
<template>
<div>
<div>{{ arr[0] }}</div>
</div>
</template>
此时操作 arr.push(1) Vue3不会更新,Vue2组件会更新
对象
Vue2中数据的增删同样是通过队形本身的__ob__进行收集的,像 has, for...in 依赖整个对象的变化同样会造成一些不必要的更新
// obj: { a: 1}
<script>
export default {
props: ['obj'],
}
</script>
<template>
<div>
<div>{{ obj.a }}</div>
</div>
</template>
此时操作给obj增加属性 Vue3不会更新,Vue2组件会更新
// 增加属性
this.$set(this.obj, 'c', 1) // vue2
this.obj.c = 1 // vue3