前言
- 文章结构采用【指出阶段目标,然后以需解决问题为入口,以解决思路为手段】达到本文目标,若使诸君稍有启发,不枉此文心力^-^
- 文分【思路篇】和【实现篇】,本文是思路篇,建议看两个窗口同步阅读-》vue2.0|实现篇|依赖收集
目标
第一阶段:Vue.mixin实现及生命周期实现
Vue文档:mixin
先看使用
## index.html
Vue.mixin({
created(){
console.log("mixin created");
}
})
Vue.mixin({
created(){
console.log("mixin2 created");
}
})
let vm = new Vue({
el: '#app',
data(){
return {
name: '王志远',
age: 23
}
},
created () {
console.log("my created");
}
})
## 将会打印
mixin created
mixin2 created
my created
核心问题
- 针对不同选项,如何实现不同的合并策略;
- 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
- 同名钩子函数将合并为一个数组,因此都将被调用
- 控制优先级: Vue.mixin内函数,即混入对象的钩子将在组件自身钩子之前调用。
解决思路
- 策略模式,定义一个策略类(对象),根据不同key对应不同的处理函数
- 在Vue类上挂载options属性用于存储“混入”的数据或函数
- 多次使用mixin时会先将多个mixin对象合并成一个对象,Vue.options指向此对象
- 在Vue初始化数据前将Vue.options与用户传递过来的配置对象进行合并,从而完成mixin操作
- 对于生命周期的实现,如果理解了之前【vue2.0|思路篇|数据劫持】、【Vue|思路篇|编译ast】,不难理解,其实就是在特定的地方执行用户传递的回调;以beforeCreate为例:
- 在Vue执行_init方法时,需要执行initState函数,在此函数调用前执行即可
- 需注意,由于存在mixin的可能,回调必是采用数组的形式
第二阶段:watcher+dep实现依赖收集(对象)
核心问题
- 如果我们使用类似watch、或者$watch之类的API呢?这就要求属性改变不止渲染视图,还需要执行其他函数,如何实现?
- 一个属性对应一个dep很简单 ,在属性的define里形成一个闭包就好了;但dep怎么收集属性需要的watcher呢?
- 如何避免存入多个相同的watcher?比如我取了多次相同的属性,自然会导致多次收集操作
解决思路
-
一对多解耦合用发布订阅,引入Dep和Watcher的概念,Watcher是依赖,可以理解为函数;Dep则是承载每个属性对应依赖的容器,可以理解为数组;则有 属性 : dep : watcher== 1: 1 : n
- 在属性被取值时订阅当前的函数 也就是Watcher,将之存入此属性对应的Dep实例中
- 在属性取值时发布,执行当前属性对应的dep中的所有watcher
- 注意对象和数组的依赖触发逻辑的不同,如前所言,数组本身的观测只是重写了方法
-
dep收集属性需要的watcher
- 在我们取值的时候,触发get,那也就是说,我们只要在get中能拿到需要存的watcher,就可以存入了;
- 怎么存呢?这就是我们的Watcher是一个对象而不是直接一个函数的原因,它内部并不是直接执行函数的,而是先将当前watcher(也就是自身)挂载到Dep类的target属性上
- 在get时如果Dep.target存入,属性对应实例就将其存入自己的dep中
-
每个watcher和dep都有自己的id,id自增,存入时如果id相同则不存入(借助Set数据结构对ID进行避免重复)
第三阶段:watcher+dep实现依赖收集(数组)
核心问题
数组由于数据劫持是特殊处理,采用装饰方法的手段,所以在依赖收集时也需要特殊处理
解决思路
- 通过observe返回值获取到观察者对象,在观测者对象上挂载一个dep属性,这样就可以在这个dep上进行依赖收集
- 在数组重写方法的逻辑中去通过__ob__.dep获取到收集到的依赖,并发布,从而实现数组的依赖收集