Skip to content

数组响应式

数组变更方法的响应式

javascript
// 当对象中 value 是数组时,要实现响应式,只需要在变更方法内部调用 dep.notify 通知
// render watcher 更新即可,但 value 并没有收集 render watcher,而 key 会收集
// render watcher,我们需要让 value 也有 dep,因此在 Observer 类的构造器中
// this.dep = new Dep(),进入这里的 value 只可能是数组或对象,对象我们也给它 dep,
// 因为之后 Vue.set() 要用到。
// 如何让 ob.dep 收集 watcher,由于 key 会收集 render watcher,在
// defineReactive(obj, key, value) 中,因为 value 可能是数组或对象,需要实现数组或
// 对象所有层次的对象的属性的劫持,会 observe(value),而 observe 会返回 ob,那么只有
// value 是数组或对象时 ob 才为真,通过 childOb 接收这个 ob,key 通过 dep.depend() 
// 收集 render watcher,收集完后我们判断下 childOb 是否为真,为真说明 key 对应的 value 
// 是数组或对象,通过 childOb.dep.depend() 完成 value 收集 render watcher,这样当
// 调用变更方法时,内部 this.__ob__.dep.notify() 就能实现数组变更方法的响应式更新
class Observer {
  constructor (val) {
    // 给数组或对象 添加一个 dep 属性
    // 作用1:Vue.set() 实现响应式
    // 作用2:数组变更方法实现响应式
    this.dep = new Dep
  }
}

arrayMethods[method] = function (...args) { 
  const ob = this.__ob__ // 获取ob
  
  // ...
  
  ob.dep.notify() // 通知 render watcher 更新
}

function defineReactive(
  obj: object,
  key: string,
  val?: any
) {
  // 每个属性收集使用它们的 watcher
  const dep = new Dep()

  // 需要实现数组或对象所有层次的对象的属性的劫持
  // childOb 是 Observe 实例, val 是数组或对象时 childOb 才有值
  let childOb = observe(val, false, mock)
  // 为该属性实现响应式
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = val
      // 如果模板外使用数据,不进行收集操作。
      if (Dep.target) {
        // 互相收集
        dep.depend()
        // value 是数组或对象
        if (childOb) {
          // 数组变更方法响应式 & Vue.set()
          childOb.dep.depend()
        }
      }
    }
  }
}

数组嵌套的响应式

javascript
// 外层数组 dep 中收集了 render watcher, 变更方法中通过 ob.dep.notify() 能更新视图,
// 内层数组, observer(value) 时会产生它的 dep,然后走 observeArray(),不会进入
// defineReactive 中,这也就是为什么数组里只有对象才会被劫持。因此访问内层数组时不触发
// get,内层数组的 dep 收集不到任何 render watcher,变更方法中 ob.dep.notify()不能
// 更新视图。但外层数组是对象的属性,在外层数组的 dep 收集 render watcher 时,如果
// value 是数组,调用 dependArray 方法让这个数组内部的数组收集 render watcher,
// dependArray 遍历数组每一项 item,如果某项有 __ob__ 属性,就 item.__ob__.dep.depend()
// 收集 watcher,由于还可能再嵌套,如果 item 是数组,递归调用 dependArray 让所有层次
// 的数组都收集到 render watcher,这样内层数组调用变更方法时就能实现视图的更新了。
function defineReactive(
  obj: object,
  key: string,
  val?: any
) {
  // 每个属性收集使用它们的 watcher
  const dep = new Dep()

  // 需要实现数组或对象所有层次的对象的属性的劫持
  // childOb 是 Observe 实例, val 是数组或对象时 childOb 才有值
  let childOb = observe(val, false, mock)
  // 为该属性实现响应式
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = val
      // 如果模板外使用数据,不进行收集操作。
      if (Dep.target) {
        // 互相收集
        dep.depend()
        // value 是数组或对象
        if (childOb) {
          // 数组变更方法响应式 & Vue.set() 所有层次属性的响应式
          childOb.dep.depend()
          // 数组嵌套时内部数组变更方法响应式
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
    }
  })
}

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