写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
如果对依赖收集完全没有概念的同学,可以先看我这篇
依赖收集,主要是为了解决一个问题,什么问题呢?
首先,我们都知道,Vue 的数据是响应式更新的,一旦数据改变了,那么相应使用到 数据的地方也会跟着改变。
那么问题来了,数据改变的时候,Vue 怎么知道,去让那些使用到数据的地方也改变呢?
这就是依赖收集要解决的问题!
他是怎么解决的?简单说就是把依赖了数据的地方,给集中收集起来以便变化后通知!我们今天来看源码的流程
首先,响应式更新,分为两步,依赖收集和依赖更新
今天讲的是依赖收集,如何去收集 使用了数据的地方
依赖收集,又分为两个流程
1、数据初始化流程
2、依赖收集流程
当前篇,先以基本数据类型为例讲解,因为 基本数据和 引用数据 在处理上有很大的不同,引用类型需要理解的东西更多更复杂,所以需要循序渐进,分两篇描述
数据初始化流程
首先,在实例初始化的时候,需要对数据进行响应式处理,也就是给每个属性都使用 Object.defineProperty 处理
处理的流程是怎么样的呢?
1、实例初始化中,调用 initState 处理部分选项数据,initData 用于处理选项 data
Vue.prototype._init=function(){
...
initState(this)
...
}
function initState(vm) {
var opts = vm.$options;
... props,computed,watch 等选项处理
if (opts.data) {
initData(vm);
}
};
2、initData 遍历 data,definedReactive 处理每个属性
function initData(vm) {
var data = vm.$options.data;
data = typeof data === 'function' ?
data.call(vm, vm) : data || {};
// ... 遍历 data 数据对象的key,重名检测,合规检测等代码
new Observer(data);
}
function Observer(value) {
var keys = Object.keys(value);
// ...被省略的代码
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
3、definedReactive 给对象的属性 通过 Object.defineProperty 设置响应式
function defineReactive(obj, key) {
// dep 用于中收集所有 依赖我的 东西
var dep = new Dep();
var val = obj[key]
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() { ...依赖收集,详细源码下个流程放出 },
set() { ....依赖更新,源码下篇文章放出 }
});
}
依赖收集流程
写一个小例子来解析
new Vue({
el: document.getElementsByTagName("div")[0],
data(){
return {
name:11
}
}
})
页面模板
页面引用了数据 name,name 需要保存 页面的watcher,以便于 name 变化时,通知 页面watcher 更新
1、页面渲染函数
with(this){
return _c('div',{},[name])
}
2、读取 name
渲染函数执行,上下文对象绑定为 实例,于是name读取到实例上的 name
3、保存 watcher
name 被读取,自然走到 Object.defineProperty.get 方法上,从这里开始收集 watcher
先来观察下 defineReactive 中省略的 get 的源码
function defineReactive(obj, key) {
var dep = new Dep();
var val = obj[key]
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
// 收集依赖
dep.addSub(Dep.target)
}
return val
}
});
}
哈哈哈,这里就有意思了,这段代码就是 依赖收集的核心,主要是三个点
Dep.target
Dep
dep.addSub
1、Dep.target
Dep.target 指向的是各种 watcher,watch的watcher,页面的watcher 等等
Dep.target 是变化的,根据当前解析流程,不停地指向不同的 watcher (指向,其实就是直接赋值 ,如下)
Dep.target = 具体watcher
“当然没有这么简单,就是表示一个意思而已”
简单想,指向哪个watcher,那么就是那个 watcher 正在使用数据,数据就要收集这个watcher
你可以先不用管 Dep.target 到底是怎么指向,你只用记住 在 watcher
比如当前页面开始渲染时,Dep.target 会提前指向当前页面的 watcher。
于是页面渲染函数执行,并引用了数据 name 后,name 直接收集 Dep.target,就会收集到当前页面的 watcher
watcher 有负责实例更新的功能,所以会被收集起来,数据变化时通知 watcher,就可以调用 watcher 去更新了
watcher 在 依赖收集中只起到被收集的作用,所以不会在这里详细解释
2、Dep
Dep 是一个构造函数,用于创建实例,并带有很多方法
实例会包含一个属性 subs 数组,用于存储不同数据 【收集的依赖】
看下dep的构造函数
var Dep = function Dep() {
// 保存watcher 的数组
this.subs = [];
};
3、dep.addSub
原型上的方法,作用是往 dep.subs 存储器中 中直接添加 watcher
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
};
所以,【dep.addSub(Dep.target) 】就会直接添加当前 watcher
于是,收集流程大概是这样
1、页面的渲染函数执行, name 被读取
2、触发 name的 Object.defineProperty.get 方法
3、于是,页面的 watcher 就会被收集到 name 专属的闭包dep 的 subs 中
总结
为什么需要依赖收集,之前也已经说过,是为了变化时通知 那些使用过数据的地方
就好比,你去商店买东西,东西还没有发售,于是你把你的电话给老板,老板把你的记在电话本上。当东西发售时,就会打你的电话通知你,让你来领取(完成更新)。
其中涉及的几个步骤,按上面的例子来转化一下
1、你买东西,就是你要使用数据 name
2、你把电话给老板,电话就是你的 watcher,用于通知
3、老板记下电话在电话本,就是把 watcher 保存在 subs 中。
4、剩下的步骤属于依赖更新