Vue源码解析13--core.observer-观察者

Observer观察者

Vue要定义一个响应式的属性,本质上是改造其getter/setter。

对于getter,具体的操作是:

  1. 执行原本的getter(如果用户做了定义),获得value。
  2. 如果是响应式的属性,执行依赖收集,即将该dep加入watcher.deps中,将watcher加入dep.subs中,交叉引用(当然也要做查重,避免加入重复的引用)。
  3. 深度(deep)模式下,它的成员也有observer,执行它对应的依赖收集。成员为Array的情况下,遍历它,并执行依赖收集。
  4. 返回 value。

对于setter,具体的操作是:

  1. 先触发getter,获得上一次的值,并收集依赖。
  2. 对比当前值和上一次的值,如果没变化,直接返回。
  3. 如果发生了变化,执行原本的setter(如果用户做了定义),完成赋值操作。
  4. 深度(deep)模式下,尝试为成员创建Observer。
  5. 操作它的dep执行notify()方法,进而触发该dep下所有watcherupdate()方法

对于一个vm的更新,就可以描述为(假设修改了$data中的某个属性值):

  1. 触发该属性改造后的setter。
  2. setter中触发getter进行依赖收集,确认传入的值和之前不同,先执行原来定义的setter,然后触发这个属性的dep(在defineReactive()的闭包中创建)发出notify()更新通知。
  3. 根据该depdep.subs的记录,触发所有订阅这个depwatcherupdate方法。
  4. watcher.update()中判断执行类型,通常$data属性改变触发的是由scheduler负责的queueWatcher(this)异步更新。
  5. scheduler将这个watcher加入更新队列,nextTick()中flush整个队列,调用watcher.run()来执行更新。
  6. watcher.run() 中执行该watcherwatcher.get()方法。
  7. watcher.get()中,执行this.getter(),即在lifecyclemountComponent()在创建这个Watcher实例时,传入的updateComponent()方法,触发vm._update(vm._render(), hydrating)
  8. vm._render()负责生成新的vnodevm._update()对比新vnode和原vnode,执行patch算法,并将其挂载到组件的实例上去,更新完成。

建模

对于整个Observer模块,共有四个核心概念

  1. Observer类,附着到每个被观察的对象,作为其__ob__属性,负责改造其属性为getter/setter形式。dep属性为其Dep,用于收集依赖并发出更新通知。
  2. Dep类,负责依赖收集和发布更新通知,可以让多个watcher去订阅它。它记录了所有订阅它的watcher,放在subs中。其静态属性Dep.target用于指示当前正在执行的watcher。
  3. Watcher类,解析getter表达式,收集依赖,并且在getter所指向的值发生改变时触发回调。computedWatcher放在vm._computedWatchers中,其他watcher放在vm._watchers中,vm中负责render的watcher为vm._watcher
  4. Scheduler调度器,用于记录当前tick下需要进行异步更新的watcher队列,调度这些watcher的异步更新,在nexttick()中执行。执行完成后,触发对应vm的activatedupdated钩子,并清理现场。

Vue源码解析12--core.instance.proxy-ProxyAPI的尝试

Proxy API

在非Prod环境下,使用ES6的Proxy API来做一些开发时的行为拦截。

包括拦截对内建值的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
let initProxy

if (process.env.NODE_ENV !== 'production') {
const allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
)

const warnNonPresent = (target, key) => {
warn(
`Property or method "${key}" is not defined on the instance but ` +
'referenced during render. Make sure that this property is reactive, ' +
'either in the data option, or for class-based components, by ' +
'initializing the property. ' +
'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
target
)
}

// 指示浏览器环境是否支持Proxy
const hasProxy =
typeof Proxy !== 'undefined' && isNative(Proxy)

// 支持Proxy情况下,对config.keyCodes的set进行拦截,不允许对Vue内建的一些按键值进行修改
if (hasProxy) {
const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
config.keyCodes = new Proxy(config.keyCodes, {
set (target, key, value) {
if (isBuiltInModifier(key)) {
warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
return false
} else {
target[key] = value
return true
}
}
})
}

const hasHandler = {
has (target, key) {
const has = key in target
const isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_')
if (!has && !isAllowed) {
warnNonPresent(target, key)
}
return has || !isAllowed
}
}

const getHandler = {
get (target, key) {
if (typeof key === 'string' && !(key in target)) {
warnNonPresent(target, key)
}
return target[key]
}
}

initProxy = function initProxy (vm) {
if (hasProxy) {
// 选择使用getHandler还是hasHandler
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
}

Vue源码解析11--core.instance.inject-上层依赖

依赖注入

provideinject需要配套使用。

provide用于高阶组件向所有内部组件注入依赖,依赖对象保存在高阶组件的vm._provided字段上。

inject用于子组件从最近的一级上获得对应依赖。

initProvide

初始化provide,记录在vm._provided上。provide可以传入function,需要调用一下获得结果。

依赖的名称一般为string,也可以使用Symbol,但是需要浏览器环境的支持。

1
2
3
4
5
6
7
8
export function initProvide(vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}

initInjections

初始化injections。

对于每个依赖,从组件自身开始,遵循就近原则,逐层向上,在上级组件的_provided属性中寻找对应依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
export function initInjections(vm: Component) {
// 寻找依赖
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
// 将每个依赖改造为响应式的,并提示不要进行直接的修改
Object.keys(result).forEach(key => {
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm,
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}

/**
* 寻找对应的依赖
* @param inject
* @param vm
* @return {any}
*/
export function resolveInject(inject: any, vm: Component): ?Object {
if (inject) {
const result = Object.create(null)
// 过滤出可枚举的属性
const keys = hasSymbol
? Reflect.ownKeys(inject).filter(key => Object.getOwnPropertyDescriptor(inject, key).enumerable)
: Object.keys(inject)

for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = inject[key].from
let source = vm
/**
* 从自身开始,遵循就近原则,逐层向上,在上级组件的_provided属性中寻找对应依赖
*/
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}

/**
* 如果没找到,检查该依赖有没有设置default,如有则使用default
* 如果没有default,就要提示没有找到依赖
*/
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}