前言
我还是感觉看了个寂寞, 一直分析代码执行, 但是忽略了比较宏观的一些事, 比如Dep
如何将变化通知到Watcher
, Watcher
是怎么订阅Dep
的, 缺乏这种意识.
一、由initData整理思路
1.1 initData()到observe()
从vm
提取data
数据对象, 传给observe
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
observe(data, true)
}
1.2 observe()
检查该data
数据对象是否已受观察, 即检查该数据对象内是否具备__ob__
属性, 若有则说明已受观察不做处理, 若无则设置观察者new Observer(value)
.
可能因为是初始化, 所以只对没有被观察的设置观察.
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
1.3 new Observer()
将Dep
, 数据对象, vmConunt
传入def()
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this) // 数据对象, Dep, vmCount
this.walk(value)
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
}
1.4 def()
核心defineProperty()
为数据对象value
设置__ob__
属性, 至此该对象被Dep劫持
Object.defineProperty(value, '__ob__', { Dep, 数据对象, vmCount })
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
完成后value
上具备__ob__
属性.
// value
{
xxx: 'xxx',
x: 'xxx',
__ob__: {
dep: {
...
subs: [
订阅者0,
订阅者1,
...
]
}
}
}
此时Observer
又调用了walk(value)
1.5 walk()
def
完成后value
会带着Dep
来执行walk()
提取数据对象的key构成数组, 遍历数组对其内部元素defineReactive(数据对象, 属性名)
将数据对象中每个属性都执行defineReactive()
.
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
1.6 defineReactive()
defineReactive(数据对象, 属性名)
, 内部Object.property(数据对象, 属性名, {..., get, set})
为数据对象设置get
和set
方法, 在初始化的时候会触发get
, 后期更新数据会触发set
.
get
即初始化时检查Dep.target
, 这个变量就是订阅该Dep
的一个Watcher
, new Watcher()
时会触发Watcher
类的get()
执行dep
的pushTarget(this)
, 这个this
指向一个Watcher
, 随后pushTarget()
内Dep.target = 这个Watcher
, 从而检索成功放行.
放行后执行dep.depend()
调用该dep
的depend()
方法进而将这个dep
加入到Dep.Target对应的Watcher
的newDeps
数组内, 表示这是该Watcher
订阅的Dep
, 同时Dep
也会调用addSub()
将这个Watcher
加入自己的Subs
数组表示这是自己的订阅者.
每个属性对应一个负责劫持的Observer
, 每个Observer
通知一个Dep
, 一个Dep
可以由多个Watcher
订阅, Dep
也通知变化到多个Watcher
.
数据更新时会触发set
进而调用Dep
的notify()
通知函数, 通知订阅该Dep
的所有Watcher
执行update()
, 下面会说到.
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
dep.notify()
}
})
}
1.7 new Dep() && depend()
defineReactive()
中defineProperty()
定义的get()
内部由Observer
实例调用Dep
的depend()
, 在depend()
会调用Watcher
类的addDep()
方法, Watcher
借此拿到Dep
.
addDep
还调用Dep
的addSub()
, Dep
借此拿到Watcher
, 此时双方订阅与被订阅关系构成.
Dep
的Subs
数组用来存放所有订阅者Watcher
.
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
1.7.1 pushTarget()
Dep.target = null // 全局
const targetStack = [] // 全局
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
1.8 Watcher
Watcher
实例化先调用get()
里的pushTarget()
(这个函数也在dep.js中但不属于Dep中)传自己过去, 然后pushTarget
将Watcher
赋值给Dep.target
, 此时defineReactive()
里的dep.depend()
能够执行.
直到newDep()
发生, Dep
的depend(this)
执行Dep.target.addDep()
(addDep()
是Watcher
的方法, 两者借此建立联系), 双方各自把对方加进自己的数组newDep
和Sub
, 订阅算是完成.
export default class Watcher {
get () {
pushTarget(this) // dep里的pushTarget
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep) // Watcher拿到Dep
if (!this.depIds.has(id)) {
dep.addSub(this) // Dep拿到Watcher
}
}
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
run () {
if (this.active) {
const value = this.get()
}
}
}
1.9 initData图示
二、由initComputed整理思路
2.1 由initComputed()到defineComputed()
整个主体在一个loop完成, 为每个计算属性实例化Watcher
, vm._watchers.push(this)
将该Watcher存入vm的诸多Watcher
中.
然后对每个计算属性执行defineComputed(vm, 计算属性名, 计算属性值)
const sharedPropertyDefinition = { // 全局
enumerable: true,
configurable: true,
get: noop,
set: noop
}
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
2.2 defineComputed()
针对computed
属性值的不同书写方式组成sharedPropertyDefinition
作为该计算属性的描述符参数.
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
2.3 createComputedGetter()
在上一步为每个计算属性实例化了Watcher
, 拿到当前计算属性的Watcher
, 执行Watcher
的evaluate()
调用Watcher
的get()
和Dep的pushTarget
, 导致Dep.target = 当前Watcher
Dep.target
存在后由Watcher
调用Dep的depend()
, 然后就回到1.8的addDep()
订阅了.
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
2.4 initComputed图示
总结
每次到addDep()
, 也就是每次Watcher
调用Dep
的depend()
, 都会进行双向数据收集, Watcher和Dep互相同步数据, Dep的数据更新后通知订阅自己的每个Watcher调用notify()
进而调用update()
更新数据, computed计算属性在接受
defineProperty()之后会拥有
set()和
get()方法, 更新调
set()之后
Dep`通知.
上一篇: Vue 2.6.13 源码解析(三) 初始化
下一篇: Vue 2.6.13 源码解析(五) 感知变化以发起通知