Vue.js 源码(3)—— Array 的变化侦测:揭秘数据变更的秘密
2023-11-06 00:59:15
前言
之前我们学习了 Object 的侦测变化,那么为什么 Array 要单独来讲呢?我们用下面的例子来说明一下:
const obj = {
name: '张三'
}
const vm = new Vue({
data: {
obj
}
})
vm.obj.name = '李四'
当我们改变 obj.name
的值时,Vue.js 能够自动检测到变化,并更新视图。这是因为 Object 可以通过 getter/setter 来实现状态的侦测。
Object.defineProperty(obj, 'name', {
get() {
return this._name
},
set(newName) {
this._name = newName
// 通知 Vue.js 数据已发生变化
this.__ob__.dep.notify()
}
})
然而,数组的 push/pop/shift/unshift 等操作并不会触发 getter/setter,因此我们需要使用另外一种方式来实现数组的变化侦测。
Array 的变化侦测原理
Vue.js 通过数组的 __ob__
属性来实现变化侦测。__ob__
属性是一个 Observer 实例,它包含了一个 dep
属性,dep
属性是一个 Dep 实例,它负责收集依赖于该数组的 Watcher 实例。
当我们对数组进行 push/pop/shift/unshift 等操作时,Vue.js 会通过 __ob__
属性通知 dep
属性,dep
属性再通知依赖于该数组的 Watcher 实例,从而触发视图更新。
Array 的变化侦测优化
为了提高数组变化侦测的性能,Vue.js 在数组的 __ob__
属性上使用了 dep.len
属性来记录数组的长度。当我们对数组进行 push/pop/shift/unshift 等操作时,Vue.js 只需要更新 dep.len
属性,而不必遍历整个数组来通知 Watcher 实例。
Array 的变化侦测局限性
由于 Vue.js 是通过数组的 __ob__
属性来实现变化侦测的,因此如果我们直接修改数组的元素,Vue.js 是无法检测到变化的。例如:
const arr = [1, 2, 3]
const vm = new Vue({
data: {
arr
}
})
arr[0] = 4
当我们修改 arr[0]
的值时,Vue.js 无法检测到变化,视图也不会更新。这是因为 Vue.js 只会侦测数组的长度变化,而不会侦测数组元素的变化。
为了解决这个问题,我们可以使用 Vue.set()
方法来修改数组的元素。Vue.set()
方法会通知 Vue.js 数据已发生变化,从而触发视图更新。
const arr = [1, 2, 3]
const vm = new Vue({
data: {
arr
}
})
Vue.set(arr, 0, 4)
当我们使用 Vue.set()
方法修改 arr[0]
的值时,Vue.js 能够检测到变化,并更新视图。
总结
在 Vue.js 中,Array 的变化侦测是一个关键机制,它确保了数据变更时,视图能够及时更新。Vue.js 通过数组的 __ob__
属性和 dep
属性来实现变化侦测,并通过 dep.len
属性来优化性能。然而,Vue.js 无法检测到直接修改数组元素的变化,因此我们需要使用 Vue.set()
方法来修改数组的元素。