Vue源码解析10--core.instance.state-实例属性与方法

实例属性与方法

建模

一个Vue的实例在初始化属性与方法时,经历了以下步骤:

  1. 初始化props,建立vm上的代理
  2. 初始化methods,直接挂载到vm上,不设代理
  3. 初始化data,建立vm上的代理。由于data一般情况下是函数,要做一次getData()执行来获得对象。
  4. 初始化computed,直接挂载到vm上,不设代理,vm._computedWatchers记录它们的内部watcher
  5. 初始化watch,不挂载,vm._watchers记录每个watch属性的watcher(也包括组件本身的watcher)
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
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options

// 初始化props
if (opts.props) initProps(vm, opts.props)

// 初始化methods
if (opts.methods) initMethods(vm, opts.methods)

// 初始化data,没有data时假定为空对象
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}

// 初始化computed
if (opts.computed) initComputed(vm, opts.computed)

// 初始化watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}

Proxy属性代理

为了可以方便地在vm上去访问这些属性和方法,也就是使用vm.key,而不是使用vm._data.key的方式,就需要在vm上提供一个该属性或方法的代理引用。

它们的指向依然是_data_props_methods等字段上的对应属性,只是为了使用上的方便而做了这个代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop,
}

/**
* 通过proxy函数将_data(或者_props等)上面的属性代理到vm上,
* 从而直接通过 this.key 访问,而无需 this._data.key 的形式,
* 本质上是改造vm上这个对应key的setter和getter,
* 最终set和get操作的还是_data、_props等对象上面的属性
*/
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}

初始化props

每个props的属性在初始化时,会经历以下步骤:

  1. 将props的key缓存在vm.$options._propKeys,更新时遍历这个Array来取key,提高效率
  2. 利用core/util/props提供的validateProp来验证prop
  3. 检查有没有重名的字段
  4. 通过defineReactive()方法,改造getter和setter,纳入观察
  5. vm上对每个prop挂载代理
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
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}

// 将props的key缓存在vm.$options._propKeys,props更新时可以直接遍历这个Array,而不是枚举对象属性
const keys = vm.$options._propKeys = []

// 如果$parent为null或undefined,说明是根节点
const isRoot = !vm.$parent
// 非root节点,可以在最后将shouldConvert赋值为true
if (!isRoot) {
toggleObserving(false)
}

/**
* 将props上的属性代理到vm上
*/
for (const key in propsOptions) {
// 将key存入vm.$options._propKeys
keys.push(key)
// 验证prop
const value = validateProp(key, propsOptions, propsData, vm)

/**
* 改造getter和setter,纳入观察
*/
if (process.env.NODE_ENV !== 'production') {
// 检查是否为保留的字段
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm,
)
}
// 在setter中提示不要在子组件中直接修改props的属性,而是让父组件传入
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm,
)
}
})
} else {
defineReactive(props, key, value)
}

// Vue.extend()期间,静态props已经被代理到组件原型上,因此只需要代理props
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}

初始化methods

methods 中定义的方法不需要做响应式处理,因此也不需要做代理,直接验证没有重名的情况下,挂载到 vm 上,并 bind 它的 this 指针为 vm。

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
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
// 保证不是null或者undefined
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm,
)
}
// 保证不和props的属性重名
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm,
)
}
// 保证不是vm保留字段
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`,
)
}
}
// 方法可以直接挂载,无需proxy,注意bind它的this指针到vm实例
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
}
}

初始化data

由于data通常是一个function,所以要使用getData()方法来得到它的返回值,作为初始值。它的过程为:

  1. 在定义了data的情况下,调用getData()获得返回值,作为data初始值
  2. 检查是否重名
  3. vm上挂载data各个属性的代理
  4. 使用observe方法,将data加入观察
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
function initData(vm: Component) {
let data = vm.$options.data
/**
* data通常应当为function,执行后得到返回对象
*/
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm,
)
}

/**
* 将data代理到vm实例上,
*/
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
// 保证不与methods中的属性重名
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm,
)
}
}
// 保证不与props中的属性重名
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm,
)
}
// 保证不是vm的保留字段
else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}

// 将data加入观察
observe(data, true /* asRootData */)
}

export function getData(data: Function, vm: Component): any {
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}

初始化computed

computed的取值本质上是基于脏值检查的,只是get的触发时机通常由data和props控制。

computed的创建过程为:

  1. 提取每个属性的getset方法,仅传入function则认为只有get
  2. 建立其内部的watcher,保存在vm._computedWatchers
  3. 检查是否重名
  4. 改造computed属性的getter,使用watcher.depend()来收集依赖,watcher.evaluate()来做脏值检查。
  5. 直接挂载到vm实例上,无需创建额外的代理
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const computedWatcherOptions = {computed: true}

function initComputed(vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()

/**
* 计算属性可能是一个function,也有可能设置了get以及set的对象,
*/
for (const key in computed) {
const userDef = computed[key]
/**
* 只是一个function,说明只有getter,否则就要从对象中提取get
*/
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm,
)
}

if (!isSSR) {
/**
* 为computed的属性创建内部watcher,保存在vm实例的_computedWatchers中
* 这里的computedWatcherOptions参数传递了computed: true
*/
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions,
)
}

// 避免重复定义同名的属性
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}

/**
* 定义单个computed属性
* @param target
* @param key
* @param userDef
*/
export function defineComputed(target: any, key: string, userDef: Object | Function) {
// SSR情况下无需watch
const shouldCache = !isServerRendering()

/**
* 单传function,就是只有get
* getter会被进行改造,
* 调用对应watcher的depend()方法进行依赖收集,调用evaluate()方法返回最新的值
*/
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop
}
// 没有定义setter的时候,给一个默认的setter,提示不能对它进行set
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this,
)
}
}
// 直接将其定义到实例上
Object.defineProperty(target, key, sharedPropertyDefinition)
}

/**
* 创建计算属性的getter
* @param key
* @return value
*/
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 依赖收集
watcher.depend()
// 脏检查,获取最新的数值
return watcher.evaluate()
}
}
}

初始化watch

watch字段只是一个语法糖,本质上是利用Vue.prototype.$watch()方法来创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {
const vm: Component = this
// 对象形式的callback,让createWatcher()去解包
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}

options = options || {}
options.user = true

// 给它建立一个watcher,做监测,会在vm._watchers中加入这个watcher
const watcher = new Watcher(vm, expOrFn, cb, options)

// 设置immediate: true的时候会立即执行一次
if (options.immediate) {
cb.call(vm, watcher.value)
}

// 返回unwatch()的方法,用于解除这个watch,停止触发回调
return function unwatchFn() {
watcher.teardown()
}
}

watch的handler支持Function | ObjectArray<Function | Object>形式。

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
function initWatch(vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}

/**
* 创建单个watch,利用Vue.prototype.$watch()方法来进行监测
* @param vm
* @param expOrFn
* @param handler
* @param options
* @return {Function}
*/
function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {
// 对对象形式的handler做解包
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 对字符串形式的handler,认为它是一个key,从vm上找到对应的method
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}

State的混入

向Vue提供原型属性和方法,包括:

  • $data$props属性
  • $set()$delete()方法
  • $watch()方法
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
export function stateMixin(Vue: Class<Component>) {
const dataDef = {}
dataDef.get = function () {
return this._data
}
const propsDef = {}
propsDef.get = function () {
return this._props
}
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function (newData: Object) {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this,
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
// 提供$data和$props属性,作为_data和_props的代理
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)

// 向响应式对象添加响应式的属性
Vue.prototype.$set = set
// 与set对立,删除对象属性
Vue.prototype.$delete = del

Vue.prototype.$watch = function() {/* ... */}
}

Vue源码解析09--core.instance.lifecycle-生命周期

生命周期

建模

组件实例上使用部分属性来记录组件当前的生命周期状态:

  • _directInactive:activate或inactivate子组件时候改变
  • _isMounted:是否已挂载,mounted钩子触发时置位
  • _isDestroyed:已被释放,destroyed钩子触发时置位
  • _isBeingDestroyed:正被释放,beforeDestroy钩子触发时置位

initLifecycle()负责将$options的部分属性进行初始化,包括

  • 组件间关系描述属性:$parent$root$children
  • 组件内部使用的属性:$refs_watcher_inactive
  • 生命周期状态:_directInactive_isMounted_isDestroyed_isBeingDestroyed
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
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false

// 初始化生命周期
export function initLifecycle(vm: Component) {
const options = vm.$options

// 将vm对象存储到parent组件中,如果是非抽象组件,需要存储到最近一层的非抽象父级上
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}

// 设置组件间关系描述属性
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []

// 用于组件内使用的属性
vm.$refs = {}
vm._watcher = null
vm._inactive = null

// 生命周期状态
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}

调用声明周期钩子

callhook()用于调用钩子函数,并触发对应的钩子事件

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
// 调用钩子函数并且触发钩子事件
export function callHook(vm: Component, hook: string) {
pushTarget()

// 从vm上获取到对应hook的回调
const handlers = vm.$options[hook]

// 遍历执行对应的回调函数
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm)
} catch (e) {
handleError(e, vm, `${hook} hook`)
}
}
}

// 对于有hook事件的,发射hook事件
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}

popTarget()
}

混入生命周期

lifecycleMixin()负责改造全局Vue.prototype,添加下列方法:

  • _update
  • $forceUpdate
  • $destroy
1
2
3
4
5
6
7
8
// 混入生命周期
export function lifecycleMixin(Vue: Class<Component>) {
/*
Vue.prototype._update = ...
Vue.prototype.$forceUpdate = ...
Vue.prototype.$destroy = ...
*/
}

Vue.prototype._update

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
// 更新节点
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance

// 载入当前状态
activeInstance = vm
vm._vnode = vnode

//基于后端渲染Vue.prototype.__patch__被用来作为一个入口
if (!prevVnode) {
// 没有prevVnode,说明是初次渲染,初始化render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// 更新节点
vm.$el = vm.__patch__(prevVnode, vnode)
}

// 恢复activeInstance
activeInstance = prevActiveInstance

// 更新实例的__vue__
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}

// 如果父级是高阶组件,更新其$el
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated钩子由scheduler来触发,保证子组件在父组件的updated钩子中更新
}

Vue.prototype.$forceUpdate

1
2
3
4
5
6
7
8
// Vue.$forceUpdate实现,强制进行更新
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
// 调用其watcher的更新方法
if (vm._watcher) {
vm._watcher.update()
}
}

Vue.prototype.$destroy

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
// Vue.$destroy实现,完全销毁一个实例,清理它与其它实例的连接,解绑它的全部指令及事件监听器。
Vue.prototype.$destroy = function () {
const vm: Component = this

// 避免重复触发$destroy
if (vm._isBeingDestroyed) {
return
}
// 触发beforeDestroy钩子
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true

// 从父级中移除自身
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}

// 该组件下的所有Watcher从其所在的Dep中释放
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}

// 移除data被观察的引用
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}

// _isDestroyed状态置位
vm._isDestroyed = true
// Vnode置空
vm.__patch__(vm._vnode, null)

// 触发destroyed钩子
callHook(vm, 'destroyed')

// 将该vm上的所有事件监听器清除
vm.$off()

// 清除实例的__vue__
if (vm.$el) {
vm.$el.__vue__ = null
}

// 释放环形引用(否则不能释放内存)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}

挂载组件

挂载一个组件,会经历以下步骤:

  1. 触发beforeMount钩子
  2. 注册Watcher实例,以updateComponent为getter,进行依赖收集
  3. 触发mounted钩子
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
// 挂载组件
export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
vm.$el = el
if (!vm.$options.render) {
// render函数不存在的时候创建一个空的VNode节点
vm.$options.render = createEmptyVNode

if (process.env.NODE_ENV !== 'production') {
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm,
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm,
)
}
}
}

// 触发beforeMount钩子
callHook(vm, 'beforeMount')

// updateComponent作为Watcher对象的getter函数,用来依赖收集
let updateComponent

// 非生产环境下做performance的mark
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`

mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)

mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}

// 注册一个Watcher实例,
// Watcher的getter为updateComponent函数,用于触发所有渲染所需要用到的数据的getter,进行依赖收集
// 该Watcher实例会存在所有渲染所需数据的闭包Dep中
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
},
}, true /* isRenderWatcher */)
hydrating = false

// 触发mounted钩子,此时instance已经存在,$vnode还没有渲染
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}

更新子组件

更新子组件涉及以下几个步骤:

  1. 更新子组件对_vnode.parentVnode的引用
  2. 更新$attrs$listeners的引用
  3. 更新props,利用core/util/props.jsvalidaeProp来做验证
  4. 更新事件监听器
  5. 如果有子组件,处理slot子组件,并触发一次强制更新
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
// 更新子组件
export function updateChildComponent(
vm: Component,
propsData: ?Object,
listeners: ?Object,
parentVnode: MountedComponentVNode,
renderChildren: ?Array<VNode>,
) {
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = true
}

// 判断组件是否有slot子组件,需要在覆盖$options._renderChildren完成
const hasChildren = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
)

vm.$options._parentVnode = parentVnode
vm.$vnode = parentVnode // 在无需重新渲染情况下,更新占位的节点

if (vm._vnode) { // 更新子组件_vnode.parent的引用
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren

// 更新$attrs和$listeners。它们本身是响应式的,所以如果子组件有使用它们,可能触发子组件的更新
vm.$attrs = parentVnode.data.attrs || emptyObject
vm.$listeners = listeners || emptyObject

// 更新props
if (propsData && vm.$options.props) {
toggleObserving(false)
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props

// 使用core/util/props的validateProp()来验证其是否有效
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
// 保存原始propsData
vm.$options.propsData = propsData
}

// 更新事件监听器
listeners = listeners || emptyObject
const oldListeners = vm.$options._parentListeners
vm.$options._parentListeners = listeners
updateComponentListeners(vm, listeners, oldListeners)

// 如果有子组件,利用core/instance/render-helpers/resolve-slots.js的resolveSlots()来处理slot子组件,并触发一次强制更新
if (hasChildren) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}

if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = false
}
}

activate和deactivate子组件

执行时会触发子组件的activatedinactivated钩子 。

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
// 判断组件是否active
export function isInInactiveTree(vm) {
while (vm && (vm = vm.$parent)) {
if (vm._inactive) return true
}
return false
}

// 使所有子组件状态都变成active ,同时分别触发其activated钩子
export function activateChildComponent(vm: Component, direct?: boolean) {
if (direct) {
vm._directInactive = false
// 已经active了,就避免重复执行
if (isInInactiveTree(vm)) {
return
}
} else if (vm._directInactive) {
return
}
if (vm._inactive || vm._inactive === null) {
vm._inactive = false
for (let i = 0; i < vm.$children.length; i++) {
activateChildComponent(vm.$children[i])
}
callHook(vm, 'activated')
}
}

// 使所有子组件状态都变成inactive ,同时分别触发其deactivated钩子
export function deactivateChildComponent(vm: Component, direct?: boolean) {
if (direct) {
vm._directInactive = true
if (isInInactiveTree(vm)) {
return
}
}
if (!vm._inactive) {
vm._inactive = true
for (let i = 0; i < vm.$children.length; i++) {
deactivateChildComponent(vm.$children[i])
}
callHook(vm, 'deactivated')
}
}

Vue源码解析08--core.instance.events-事件机制

初始化事件机制

initEvents(vm)用于初始化某个vm的事件机制,被core/instance/init.js调用,用于给Vue实例注入事件机制。主要步骤为:

  1. 创建一个字典来记录所有handler,根据event名称来归类,value格式为Array<Function>
  2. 创建钩子函数标志位,表示该组件是否有生命周期钩子的回调
  3. 如果父组件有监听器,需要做初始的更新
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
/**
* 初始化输入vm的事件机制
*/
export function initEvents(vm: Component) {
// 空对象记录注册的事件,作为字典
vm._events = Object.create(null)
// 标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能
vm._hasHookEvent = false

// 获取父组件的监听器(涉及到组件间通信)
const listeners = vm.$options._parentListeners
// 如果父组件有监听器,就需要做初始的更新,
if (listeners) {
updateComponentListeners(vm, listeners)
}
}

let target: any

// 添加一个事件
function add(event, fn, once) {
if (once) {
target.$once(event, fn)
} else {
target.$on(event, fn)
}
}

// 移除一个事件
function remove(event, fn) {
target.$off(event, fn)
}

// 更新组件的监听器
export function updateComponentListeners(vm: Component, listeners: Object, oldListeners: ?Object) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, vm)
target = undefined
}

将事件机制混入到Vue.prototype上

eventsMixin()core/index.js被调用,从而将其加入到全局原型上。

1
2
3
4
5
6
7
8
9
10
export function eventsMixin(Vue: Class<Component>) {
const hookRE = /^hook:/

/*
Vue.prototype.$on = ...
Vue.prototype.$off = ...
Vue.prototype.$once = ...
Vue.prototype.$emit = ...
*/
}

Vue.prototype.$on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Vue.$on实现,添加一个事件回调
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this

if (Array.isArray(event)) {
// 对Array进行遍历,逐个递归处理
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
}
else {
// 加入对应名称的事件数组
(vm._events[event] || (vm._events[event] = [])).push(fn)

// 判断是否为钩子事件,进行对应置位
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}

Vue.prototype.$off

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
// Vue.$off实现,移除某个事件回调
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this

// 不传参数,移除所有监听器
if (!arguments.length) {
// 把_events字典整个替换
vm._events = Object.create(null)
return vm
}

// 传入数组,遍历拆分后逐个递归处理
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}

// 对于明确的单个事件移除
const cbs = vm._events[event]
if (!cbs) {
return vm
}
// 没有传入具体的handler,则删除该event名称下所有回调
if (!fn) {
vm._events[event] = null
return vm
}
// 删除某个指明的handler
if (fn) {
let cb
let i = cbs.length
// 遍历该类型的callbacks,找到后splice()删除
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
}

Vue.prototype.$once

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Vue.$once实现,只执行一次
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this

// 对原本的执行做一层闭包,先移除该监听器,后执行回调,保证了只执行一次
function on() {
vm.$off(event, on)
fn.apply(vm, arguments)
}

on.fn = fn
vm.$on(event, on)
return vm
}

Vue.prototype.$emit

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
// Vue.$emit实现,触发某个事件
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this

// 提示使用event名称小写,可以结合烤肉串命名法
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`,
)
}
}

// 遍历该类型下所有handler,逐个执行
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}