勉强画个图 理解一下 vue2.x 的响应式

859 阅读3分钟

直接上图

image.png

按图索骥

  1. 首先找到响应是的入口
    • 找到源码中的入口/core/instance/state.js, 就是在数据初始化时执行的 initData, 在该方法中发现了,执行了 observer(data) 方法,这个就是入口了
  2. 然后看看observer(data)干了什么,在 /core/observer/index.js
    • 首先干了一些判断的事儿,是不是对象了、是不是vnode 对象等等,直接跳过
    • 重要的是:实例化了一个 Observer 对象
  3. 接着,看看这个实例构造函数 Observer 是什么东西了
    • 发现对于对象的情况,执行了一个 walk 方法
    • 对于每一个数据都 执行了一个 defineReactive, 终于找到关键地方了
  4. 看看 defineReactive 方法
    • 咱只看关键部分,细节不看了(太多了)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() { 
            // 在这里收集依赖
            if (Dep.target) {
                dep.depend()
            }
        },
        set() {
            // 在这里触发依赖更新
            dep.notify()
        }
    })
    
  5. 完了,说不下去了
接着看看图吧😂, 按照图的序号接着说

先说绿色的收集依赖

  1. 创建 watcher 实例,watcher 是啥玩意儿,看起来就是用到数据的地方,就这样理解吧

  2. 执行 watcher 的 get 方法,通过 Dep.target = this 把 Dep.target 赋值为当前new 出来的 watcher 实例

  3. 执行 Dep.append 进行依赖收集,看 dep 里面是干了什么

      depend () {
       if (Dep.target) {
         // 刚刚实例化 watcher 时不是把 Dep.target 赋值成了 watcher 实例吗
         // 所以执行watcher上的addDep方法, 并把当前 dep 实例作为参数传了过去
         Dep.target.addDep(this)
       }
     }
    
  4. 进入到 Watcher 构造函数中看看,执行addDep方法

        // 有一点开始晕了,
        addDep(dep) {
            dep.addSub(this)
        }
    
  5. 发现又回到 dep 中了,执行其addSub方法, 把 watcher 放入到定义好的 sub 列表中 看到这儿有一点晕了,收集依赖,首先是 dep 执行 append 方法,然后去调用 watcher 的 addDep 方法,而 watcher,而addDep 方法 又调用了 Dep 方法从而实现了把 watcher 放入到 sub 列表中,真的是妙啊 整个流转: dep.append --> watcher.addDep(dep) --> dep.addSub(watcher); 最终实现了吧 watcher 收集在 dep 中

再说蓝色的触发更新

  1. dep.notify , 执行 dep 中的更新方法 遍历 之前收集到的所有用到该变量的地方,调用更新方法
        notify() {
             for (let i = 0, l = subs.length; i < l; i++) {
              // 执行每个watcher中的update方法,更新数据
              subs[i].update()
            }
        }
    
  2. 执行 watcher 实例的 update 方法,
  3. 把更改放入队列中 queueWatcher,等待更新

数组的更新

由于Obejct.defineProperty 方法不能读取到数组的变化,所以我们的目的解决如果知道数组发生了变化, 这样我们可以去做相应的更新视图操作, 看一下,对源码中分开的方法大概整合到了一起

    if (Array.isArray(value)) {
      // 判断是否有__proto__属性
      if (hasProto) {
        // 覆盖掉 数组原来的原型,以便实现数组的响应式
        value.__proto__  = arrayMethods
      } else {
        // 如果 没有 __proto__ 属性,就说明没有原型,就直接把修改过后的数据直接更改到数组自身
        const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
        for (let i = 0, l = arrayKeys.length; i < l; i++) {
          const key = arrayKeys[i]
          value.key = arrayMethods[key]
        }
      }
      // 这个是如果数组元素是对象的监听这个对象
      this.observeArray(value)
    }

可以看到对于数组的方法有两种处理方法,如果有原型就覆盖原型链上的方法,如果没有的话直接把当前方法修改为拦截方法,下面,就看一下具体的拦截原型的方法。

再次强调,监听数组的方法,目的是知道数组发生了变化,我们可以去作相应更新 因为我们的目的是知道数组发生了改变,所以只复写可以更改数组的七个方法,如下

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  // 这里的 def 就是 Object.defineProperty 方法,拦截了数组的操作,比如说 之前的 arr.push
  // 现在就不会走原始原型链上的方法,就会走下面的 mutator 方法
  def(arrayMethods, method, function mutator (...args) {
  // 这个是执行一个原始的数组方法
    const result = original.apply(this, args)
    // 这个 ob 是在 observe 时,把 observer 实例 放到了 数组value上的
    // 所以可以在push 方法里拿到 observer。
    const ob = this.__ob__
    // 这里,拿到插入值,是如果插入的元素是对象类型,对新增的元素作响应式处理
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // observeArray 方法是给 数组的对象元素作响应式
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 这个是数组响应式的关键
    // 因为执行了数组的方法,会走到这里
    // 然后我们去执行dep 的更新操作
    // 就是 之前 dep.notify --> watcher.update() --> queueWatcher(watcher)
    ob.dep.notify()
    return result
  })
})

这差不多就是响应式的基本内容了,刚开始梳理,有问题的地方,还烦请路过大佬 批评指正。

这里附上 vue 源码连接 vue源码