好久之前增研读过 Vue 源码,感觉精巧优雅,着实敬佩,模块拆分也让人很舒服,但是对于观察者模式的实现以及数据劫持种种原理,了解不甚透彻,近日抽时间再读,某些淤塞的节点上有了一些豁然开朗的感觉,暂时记录下来,以后慢慢详解。
Dep 模块
Dep 的实例会负责依赖管理,譬如添加订阅、删除订阅、收集订阅、通知订阅者更新
1 | let uid = 0 |
Watcher 模块
Watcher 的实例是订阅者,包括 watchs、computed,创建实例时通过lazy属性来判断是否立即添加自身到订阅器中。
1 | let uid1 = 0 |
Observer 模块
Observer 通过 observe 针对对象和数组进行观察者实例化,即在对象或数组上绑定'__ob__'
观察者实例对象,该'__ob__'
拥有一个依赖实例对象 dep,负责该对象或数组的依赖管理。
针对对象使用 defineReactive 进行数据劫持,该方法内部会创建一个闭包对 Dep 实例化。
订阅:该依赖实例在 get 方法被触发时,会通过 Dep.target 全局变量, 调用 Dep.target 指向的 watcher 实例的
addDep(this)
方法, 判断 dep.id 是否符合条件,如符合则回调dep.addSub(this)
, 将watcher自身加入dep.subs
订阅器中,实现订阅。通知:该依赖实例在 set 方法被触发时,通过
dep.notify()
通知dep.subs
中的 watcher 订阅者,执行watcher.update()
方法
1 | class Observer { |
三个模块的关系及应用
- Vue 构造函数,源码入口
1 | function Vue (options) { |
- 在 initMixin 中给 Vue.prototype 赋值 _init 函数
1 | // _init 会调用各初始化函数 |
- initRender、initInjections 会调用 defineReactive 进行数据劫持
1 | function initRender (vm) { |
- initState 调用 initProps,通过 defineReactive 数据劫持;调用 initData,通过 observe 函数创建 Observer 实例;然后调用 initWatch、initComputed 创建 Watcher 实例,将自己加入依赖对象的订阅数组中。
1 | function initState (vm) { |
- observe 函数调用 Observer 并创建实例过程中会通过 walk 函数遍历调用 defineReactive 进行数据劫持, 若传入构造函数值为数据则用 observe 递归
1 | function observe () { |
- defineReactive 是通过 Object.defineProperty 进行数据劫持的核心函数,Watcher、Dep、Observer 之间的关系也是通过它维系。
1 | function defineReactive (obj, key, val, customSetter, shallow) { |
数组方法变异
数组方法变异其实就是改写那些会改变原数组的原型方法(
'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
),具体有两步骤。- 针对可以增加项目的方法(
'push', 'unshift', 'splice'
)将其增加的参数args遍历进行observe, - 在方法返回值前,用
this.__ob__
(该数组的观察实例对象) 调用依赖this.__ob__.dep.notify()
通知变更。
- 针对可以增加项目的方法(
针对数组新增、或更改项目,提供 set 方法调用 dep.notify() 通知变更,新增将调用 defineReactive 进行订阅依赖。
- 针对数组项目删除,提供 del 方法调用 dep.notify() 通知变更
- set、del 方法会绑定 Vue 的全局变量 set、delete 和 Vue.prototype 的 $set、$delete 上。