浅谈Vue 性能优化之深挖数组(5)

set 函数接受三个参数,第一个参数可以是 Object 或者 Array,其余的参数分别为 key, value。如果利用这个 API 给 person 增加一个属性呢?

this.$set(this.person, 'gender', '男') // 组件视图重新渲染

为什么通过 set 函数又能触发重新渲染呢?注意到这一句, ob.dep.notify()ob 怎么来的呢,那就得回到之前的 observe 函数了,其实 data 经过 observe 处理之后变成下面这样。

{
 person: {
  name: '张三',
  age: 18,
  __ob__: {
   value: ...,
   dep: new Dep()
  }
 },
 __ob__: {
  value: ...,
  dep: new Dep()
 }
}
// 只要是对象,都定义了 __ob__ 属性,它是 Observer 类的实例

从 template 来看,视图依赖了 person 这个属性值,renderWatcher 被收集到了 person 属性的 Dep 实例当中,对应 defineReactive 函数定义的 语句1 ,同时, 语句2 的作用就是将 renderWatcher 收集到 person._ ob _.dep 当中去,因此在给 person 增加属性的时候,调用 set 方法才能获取到 person._ ob _.dep,进而触发 renderWatcher 更新。

那么得出结论,语句2的作用是为了能够探测到响应式数据是对象的情况下增删属性而引发重新渲染的。

再举个栗子解释下 语句3 的作用。

<div>{{books}}<div>
export default {
 data () {
  return {
   books: [
    {
     id: 1,
     name: 'js'
    }
   ]    
  }
 }
}

因为组件对 books 进行求值,而它是一个数组,所以会走到语句3的逻辑。

if (Array.isArray(value)) { // 语句3
  dependArray(value)
}

function dependArray (value: Array<any>) {
 for (let e, i = 0, l = value.length; i < l; i++) {
  e = value[i]
  e && e.__ob__ && e.__ob__.dep.depend()
  if (Array.isArray(e)) {
   dependArray(e)
  }
 }
}

从逻辑上来看,就是循环 books 的每一项 item,如果 item 是一个数组或者对象,就会获取到 item._ ob _.dep,并且将当前 renderWatcher 收集到 dep 当中去。

如果没有这一句,会发生什么情况?考虑下如下的情况:

this.$set(this.books[0], 'comment', '棒极了') // 并不会触发组件更新

如果理解成 renderWatch 并没有对 this.books[0] 进行求值,所以改变它并不需要造成组件更新,那么这个理解是有误的。正确的是因为数组是元素的集合,内部的任何修改是需要反映出来的,所以语句3就是为了在 renderWatcher 对数组求值的时候,将 renderWatcher 收集到数组内部每一项 item._