Appearance
调试数组响应式过程
调试两个过程
- 数组响应式处理的核心过程和数组收集依赖的过程
- 当数组的数据改变的时候 watcher 的执行过程
一、数组响应式处理的核心过程和数组收集依赖的过程
下面来调试数组中特殊方法的处理,然后我们再来调试数据依赖的过程:
准备html
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>
设置断点
- 第一个断点要设置到observe,也就是响应式的入口函数中。
- 第二个锻炼要设置到收集依赖的位置,这个位置在defineReactive里边。
按F5刷新页面
进行调试
当前的value是传入的data对象的值,这步要进行响应式的处理,按F11进入
Observer构造函数
执行到判断是否是数组,当前是data对象,不是数组,所以会执行walk方法
按F11进入walk方法
walk
这里面会收集所有属性调用defineReactive
defineReactive
进入的时候可以看到key就是arr属性,给这个对象创建一个dep对象,一直到observe这个位置
按F11进入
observe
因为数组也是对象,会继续执行,这个也会为数组对象创建一个Observer对象
按F11进入
给数组arr创建Observer对象
进入之后判断此value是否是数组,arr是数组判断hasProto是否支持,为true所以进入了图下的位置,可以看到arrayMethods已经对数组的修改数组的方法进行了处理
注意! arrayMethods 中的方法不是数组的原生方法,而是新定义的方法
按F11进入
protoAugment
里面将数组的原型指向了 arrayMethods 对象
这句话执行完成之后,可以看到原型进行了修改
F10跳出这个函数,会到Observer构造函数中,之后执行了observeArray方法,这个方法是循环遍历数组的每一个元素,如果是对象就进行响应式处理,这里按F10执行完毕.
这里数组处理响应式的部分已经执行完毕,下面看数组处理依赖的部分.
按F8跳到下一个断点
Dep.target
如何判断当前执行的是哪个属性呢?key上面没有提示的,我们执行的是get,其外层作用域已经销毁了,可以从调用栈中去找,可以看到当前的属性是arr
dep.depend方法就不在详细介绍了,下面看childOb,这个是之前给数组的每个属性添加响应式数据返回的observe对象,接着给属性的dep执行depend方法,添加到subs数组中和watcher数组中
对数组进行判断,如果是数组就会调用dependArray方法,按F11进入dependArray方法
dependArray
当前我们的元素都是数字,没有对象也没有数组,所以这里并不会触发
这里收集依赖的过程已经完毕
- 给当前属性收集依赖
- 如果属性的值是对象,要给对象收集依赖
- 如果属性的值是数组,要给数组的每一个元素收集依赖
我们在调试的过程中,看到如果属性发生变化会发送通知,数组元素发生变化的时候会发送通知,但是没看到其对数组的每一个属性收集依赖,所以我们修改属性的时候不会发送通知.
控制台调试
- 控制台调用vm.arr = 7,视图会发生变化
- 控制台用vm.arr[0] = 2,可以看到视图没有发生变化
- 控制台用vm.arr.push(20),可以看到视图发生了变化,并将之前修改过的值也进行了渲染
二、当数组的数据改变的时候 watcher 的执行过程
设置断点
observer/dep.js
打开页面,在dep.notify中设置断点,F5刷新页面
在控制台中修改输入vm.arr.push(100)
进入断点,首先将subs数组通过slice方法copy一份出来,所欲的通知在subs数组里面,this.subs里面可能会有新增的依赖,这边不对那些在新增的做处理.
下面F10继续执行到update
按F11进入update方法
update
this.lazy和this.sync都是false,所以执行queueWatcher函数
按F11进入,当前watcher对象没有被处理过,且队列没有正在处理的的对象flushing为false,就将当前对象push到队列中
之后看当前队列是否被执行,waiting为false,之后设置waiting为true,正在执行,开发环境执行
按F11进入之后,继续按F11找到flushSchedulerQueue函数执行的位置
首先将flush置为true,表示正在刷新队列,接下来进行排序,只有触发beforeUpdate钩子函数,之后执行watcher的run 方法,这个是核心
按F11进入,里面调用了get方法
按F11进入,按F10到了this.getter的位置,里面存储的是updateComponent,更新视图
按F11进入,这里使用了_render
和_update
这两个方法对比虚拟DOM渲染页面
这个方法执行完毕之后视图就会更新
这个时候更新视图的工作就完毕了,接下去是一些收尾的工作,run函数就执行完毕了.会到刷新队列中
下面是生产环境的一些代码,当队列完毕之后也会做一些清理工作,先备份了两个队列,接着去reset队列的状态
按F11进入resetSchedulerState
在这里面会将队列的length置为0清空队列,has对象置为空对象,不用记录watcher是否处理过,然后将waiting和flushing都置为false
之后会执行两个生命周期钩子函数,一个是组件相关的activeated钩子函数,一个是updated钩子函数
到这里,数组内容改变之后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
- 给value对象定义一个不可枚举的
- 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钩子函数