调试数组响应式过程

Vue

调试两个过程

  • 数组响应式处理的核心过程和数组收集依赖的过程
  • 当数组的数据改变的时候 watcher 的执行过程

# 一、数组响应式处理的核心过程和数组收集依赖的过程

下面来调试数组中特殊方法的处理,然后我们再来调试数据依赖的过程:

# 准备html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue.js 01 component example</title>
  </head>
  <body>
    <div id="app">
      {{ arr }}
    </div>

    <script src="../../dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          arr: [1, 2, 3]
        }
      })
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 设置断点

  • 第一个断点要设置到observe,也就是响应式的入口函数中。

image

  • 第二个锻炼要设置到收集依赖的位置,这个位置在defineReactive里边。

image

按F5刷新页面

# 进行调试

当前的value是传入的data对象的值,这步要进行响应式的处理,按F11进入

image

# Observer构造函数

执行到判断是否是数组,当前是data对象,不是数组,所以会执行walk方法

image

按F11进入walk方法

# walk

这里面会收集所有属性调用defineReactive

image

# defineReactive

进入的时候可以看到key就是arr属性,给这个对象创建一个dep对象,一直到observe这个位置

image

按F11进入

# observe

因为数组也是对象,会继续执行,这个也会为数组对象创建一个Observer对象

image

按F11进入

# 给数组arr创建Observer对象

进入之后判断此value是否是数组,arr是数组判断hasProto是否支持,为true所以进入了图下的位置,可以看到arrayMethods已经对数组的修改数组的方法进行了处理

注意! arrayMethods 中的方法不是数组的原生方法,而是新定义的方法

image

按F11进入

# protoAugment

里面将数组的原型指向了 arrayMethods 对象

image

这句话执行完成之后,可以看到原型进行了修改

image

F10跳出这个函数,会到Observer构造函数中,之后执行了observeArray方法,这个方法是循环遍历数组的每一个元素,如果是对象就进行响应式处理,这里按F10执行完毕.

image

这里数组处理响应式的部分已经执行完毕,下面看数组处理依赖的部分.

按F8跳到下一个断点

# Dep.target

如何判断当前执行的是哪个属性呢?key上面没有提示的,我们执行的是get,其外层作用域已经销毁了,可以从调用栈中去找,可以看到当前的属性是arr

image

dep.depend方法就不在详细介绍了,下面看childOb,这个是之前给数组的每个属性添加响应式数据返回的observe对象,接着给属性的dep执行depend方法,添加到subs数组中和watcher数组中

image

image

对数组进行判断,如果是数组就会调用dependArray方法,按F11进入dependArray方法

# dependArray

image

当前我们的元素都是数字,没有对象也没有数组,所以这里并不会触发

这里收集依赖的过程已经完毕

  • 给当前属性收集依赖
  • 如果属性的值是对象,要给对象收集依赖
  • 如果属性的值是数组,要给数组的每一个元素收集依赖

我们在调试的过程中,看到如果属性发生变化会发送通知,数组元素发生变化的时候会发送通知,但是没看到其对数组的每一个属性收集依赖,所以我们修改属性的时候不会发送通知.

# 控制台调试

  1. 控制台调用vm.arr = 7,视图会发生变化

image

  1. 控制台用vm.arr[0] = 2,可以看到视图没有发生变化

image

  1. 控制台用vm.arr.push(20),可以看到视图发生了变化,并将之前修改过的值也进行了渲染

image

# 二、当数组的数据改变的时候 watcher 的执行过程

# 设置断点

observer/dep.js

打开页面,在dep.notify中设置断点,F5刷新页面

image

在控制台中修改输入vm.arr.push(100)进入断点,首先将subs数组通过slice方法copy一份出来,所欲的通知在subs数组里面,this.subs里面可能会有新增的依赖,这边不对那些在新增的做处理.

下面F10继续执行到update

image

按F11进入update方法

# update

this.lazy和this.sync都是false,所以执行queueWatcher函数

image

按F11进入,当前watcher对象没有被处理过,且队列没有正在处理的的对象flushing为false,就将当前对象push到队列中

image

之后看当前队列是否被执行,waiting为false,之后设置waiting为true,正在执行,开发环境执行

image

按F11进入之后,继续按F11找到flushSchedulerQueue函数执行的位置

首先将flush置为true,表示正在刷新队列,接下来进行排序,只有触发beforeUpdate钩子函数,之后执行watcher的run 方法,这个是核心

image

按F11进入,里面调用了get方法

image

按F11进入,按F10到了this.getter的位置,里面存储的是updateComponent,更新视图

image

按F11进入,这里使用了_render_update这两个方法对比虚拟DOM渲染页面

image

这个方法执行完毕之后视图就会更新

image

这个时候更新视图的工作就完毕了,接下去是一些收尾的工作,run函数就执行完毕了.会到刷新队列中

下面是生产环境的一些代码,当队列完毕之后也会做一些清理工作,先备份了两个队列,接着去reset队列的状态

image

按F11进入resetSchedulerState

image

在这里面会将队列的length置为0清空队列,has对象置为空对象,不用记录watcher是否处理过,然后将waiting和flushing都置为false

之后会执行两个生命周期钩子函数,一个是组件相关的activeated钩子函数,一个是updated钩子函数

image

到这里,数组内容改变之后watcher的内容就调试完毕

# 三、总结

响应式处理的过程,从Vue实例的init方法中开始的

  • _init->initState(初始化Vue实例的状态)->initData(把data属性注入到Vue实例上)->observe
  • observe,将data对象转换成响应式对象
    • observe是响应式的入口
    • 接收一个参数value
    • 判断value是否是对象,如果不是直接返回
    • 判断value对象是否有__ob__,有说明已经做过响应化的处理,直接返回
    • 如果没有就创建Observer对象
    • 返回Observer对象
  • Observer
    • 给value对象定义一个不可枚举的__ob__属性,记录当前的Observer对象
    • 数组的响应式处理,改变原数组的方法改变会发送通知,找到observer中的dep.notify,在遍历每一个元素,判断是不是对象,是的话做响应式处理
    • 对象的响应式处理,调用walk方法,里面遍历每个属性,每个属性调用defineReactive
  • defineReactive
    • 每一个属性创建dep对象,收集依赖
    • 如果当前属性的值是对象,会调用observe(转换成响应式对象)
    • 定义getter
      • 收集依赖,如果属性是对象,也要为子对象收集依赖
      • 返回属性的值
    • 定义setter
      • 保存新值
      • 新值是对象就调用observe
      • 派发更新(发送通知),调用dep.notify()
  • 收集依赖
    • 在watcher对象的get中调用pushTarget记录Dep.target属性
    • 访问data中成员的时候收集依赖,这时会触发defineReactive中的getter收集依赖
    • 把属性对应的watcher对象添加到dep的subs数组中
    • 给childObj收集依赖,目的是子对象发生变化时发送通知
  • Watcher
    • 数据发生变化的时候会调用dep.notify发送通知,会调用update方法
    • 在update方法中调用queueWatcher方法,判断watcher是否被处理,如果没有就添加到queue队列中,并调用flushSchedulerQueue
    • flushSchedulerQueue(刷新任务队列的函数)
      • 触发beforeUpdate钩子函数
      • 调用watcher.run方法(渲染watcher:run->get->getter->updateComponent)
      • 清空上一次的依赖
      • 触发actived钩子函数
      • 触发updated钩子函数
更新时间: 2022-01-11 10:46