返回

Vue.js 源码(3)—— Array 的变化侦测:揭秘数据变更的秘密

前端

前言

之前我们学习了 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() 方法来修改数组的元素。