依赖收集的始末
你好! 这是一篇记载关于Vue源码中依赖收集过程的文章。
从vue初始化开始
当我们用new操作符构建一个新的Vue实例时会发生:
- 初始化,将
e
l
置
为
空
,
el 置为空,
el置为空,root 始终指向根实例,根节点唯一,且为最初声明的Vue实例,后续的组件都是 $root 的children,源码中
this.\$root = this.$parent.$root || this
,其他的就是将属性置为初始值; - 混入来自Vue构造函数的默认指令以及options,如 v-model,v-text ;
- 调用beforeCreated,此时data,computed,methods等还未初始化,故无法进行异步请求来赋值;
- 依次进行mixins混入,组件,props,methods,data,computed,watch,events 初始化,混入时读取mixins内的 options ,若原始的options 内的基本类型属性不在 mixins 内找到,则会添加,否则保留原始的数据。如果时引用类型,methods watch computed 不替换,若是生命周期,则会将mixins内的处理逻辑添加至对应钩子的事件队列中。对组件进行 init 操作得到一个 Vue 实例,将其添加至当前实例的 components 上。将对应的props,methods,data,computed绑定在vm上,不同的是 computed 和 watch 会创建 Watcher 实例,且computed是lazy,即如果依赖的数据未发生变化,会使用之前的缓存,而 data 会做数据劫持生成自己的唯一Dep;如果当前组件是子组件则会读取组件标签上的
@或者v-on
指令,并会在当前实例上通过 $on 注册事件,如果当前组建内有调用 $emit 进行触发,则会执行注册事件的回调。 - 初始化完成,调用created,此时可以通过 this 对值进行修改以及触发事件;
- 进入compile阶段;
依赖收集
- 触发途径
- 处理过程
所有通过 new 操作符调用 Watcher 生成新对象的行为都会产生依赖收集,比如computed,watch,模板文本 {{obj.a}} ,v-bind,v-model 等都会产生依赖收集行为。
当有新的Watcher实例生成时会执行get方法,而get方法内的代码是这样的:
get() {
const vm = this.vm
// 在读取值时先将观察者对象赋值给Dep.target 否则Dep.target为空 不会触发收集依赖
Dep.target = this
let value = this.getter.call(vm, vm)
if (this.filters) {
value = vm._applyFilters(value, this.filters)
}
// 触发依赖后置为空
Dep.target = null
return value
},
首先会将Dep.target设置为当前 Watcher 实例,同时访问表达式,即如果监听的是 obj.a ,则会触发 obj.a 的 get ,由于初始化时被劫持,同时生成了专属Dep ,
function defineReactive(obj, key, val) {
const dep = new Dep()
console.log(dep)
// 递归监听
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集对应的观察者对象
if (Dep.target) {
dep.depend()
console.log(dep)
if (childOb) {
childOb.dep.depend()
}
if (isArray(val)) {
for (let e, i = 0, l = val.length; i < l; i++) {
e = val[i]
e && e.__ob__ && e.__ob__.dep.depend()
}
}
}
return val
},
set(newVal) {
if (val === newVal) {
return
}
val = newVal
// 递归监听
childOb = observe(newVal)
// 触发更新
dep.notify()
}
})
}
dep会调用depend函数,将watcher加入自己的subs,同时watcher也会将dep加入自己的deps,这样当值发生变化时,调用dep.notify通知dep下所有subs中的watcher 执行 update 操作 ,而后就可以在wacher 的 callback 内拿到新旧数据了。