# vue2.x 源码摸索
# new Vue 之前(实例化 Vue 之前)
./src/core/instance/index.js
# initMixin(Vue)
将_init
函数挂载到 Vue 原型上,new Vue()
的时候执行
_init
函数会合并 options,然后依次执行initLifecycle
,initEvents
,initRender
,callHook(vm,'beforeCreate')
,initInjections
,initState
,initProvide
,callHook(vm,'created')
./src/core/instance/init.js
function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function(options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
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
# stateMixin(Vue)
./src/core/instance/state.js
将下面三个方法挂载到 vue 的原型对象上:
$set
:指向 set 方法$delete
:指向 del 方法$watch
:创建一个 watcher,返回取消 watcher 的函数
function stateMixin(Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function() {
return this._data
}
const propsDef = {}
propsDef.get = function() {
return this._props
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn() {
watcher.teardown()
}
}
}
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
# eventsMixin(Vue)
./src/core/instance/events.js
将下面四个方法挂载到 vue 的原型对象上
$on
:将注册的事件存到 vue 实例的_events 对象上$once
: 用一个函数重新包装一下回调函数,第一次执行的时候就取消事件$off
:找到 vue 实例的_events 对象,根据传参情况将注册的回调函数设为 null$emit
:找到 vue 实例的_events 对象对应的事件回调,触发回调函数function eventsMixin(Vue: Class<Component>) { const hookRE = /^hook:/ Vue.prototype.$on = function( event: string | Array<string>, fn: Function ): Component { const vm: Component = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$on(event[i], fn) } } else { ;(vm._events[event] || (vm._events[event] = [])).push(fn) // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm } 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.$off = function( event?: string | Array<string>, fn?: Function ): Component { const vm: Component = this // all if (!arguments.length) { vm._events = Object.create(null) return vm } // array of events if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$off(event[i], fn) } return vm } // specific event const cbs = vm._events[event] if (!cbs) { return vm } if (arguments.length === 1) { vm._events[event] = null return vm } if (fn) { // specific handler let cb let i = cbs.length while (i--) { cb = cbs[i] //第二个参数是绑定once的时候的拦截器函数 if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } } return vm } Vue.prototype.$emit = function(event: string): Component { const vm: Component = this 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 } }
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
# lifecycleMixin(Vue)
./src/core/instance/lifecycle.js
将下面三个方法挂载到 vue 的原型对象上
_update
:将 VNode 渲染为真实的 DOM,在首次渲染和数据更新的时候调用核心是调用
__patch__
,__patch__
函数在不同平台定义不一样,它的定义在./src/platforms/web/runtime/patch.js
,该方法的定义是createPatchFunction
的返回值,createPatchFunction
函数定义在./src/core/vdom/patch.js
中,执行该函数返回patch
函数,patch
函数通过调用原生操作 DOM 的方法并且结合算法将 DOM 元素插入到页面上$forceUpdate
:手动调用 vue 实例的 watcher.update 强制重新渲染$destroy
:
function lifecycleMixin(Vue: Class<Component>) {
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el,
vnode,
hydrating,
false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
Vue.prototype.$forceUpdate = function() {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function() {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
//从vue实例的watcher的依赖列表中删除watcher
vm._watcher.teardown()
}
//获取用户自定义的watcher
let i = vm._watchers.length
while (i--) {
//从用户的watcher的依赖列表中删除watcher
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}
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
# renderMixin(Vue)
./src/core/instance/render.js
将下面两个方法挂载到 vue 的原型对象上
$nextTick
:调用 nextTick 函数_render
:将实例渲染为虚拟 DOM其中主要是调用 render 函数(用户定义或者模版编译生成的),render 函数最终是调用 createElement 函数
createElement 函数定义在
./src/core/vdom/create-element.js
中,最终通过new VNode()
生成虚拟 DOM
执行 installRenderHelpers,该函数主要是将一些函数名简写到 Vue.prototype 对象上
function renderMixin(Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function(fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function(): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (vm._isMounted) {
// if the parent didn't update, the slot nodes will be the ones from
// last render. They need to be cloned to ensure "freshness" for this render.
for (const key in vm.$slots) {
const slot = vm.$slots[key]
if (slot._rendered) {
vm.$slots[key] = cloneVNodes(slot, true /* deep */)
}
}
}
vm.$scopedSlots =
(_parentVnode && _parentVnode.data.scopedSlots) || emptyObject
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(
vm._renderProxy,
vm.$createElement,
e
)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
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
# $mount
./src/platforms/web/entry-runtime-with-compiler.js(带 compiler)
./src/platforms/web/runtime/index.js(不带 compiler)
./src/platform/weex/runtime/index.js(跨平台)
$mount
这个方法的实现是和平台、构建方式都相关的
# 如果 options 中没有定义 render 函数,则创建 render 函数
分析./src/platforms/web/entry-runtime-with-compiler.js(带compiler)
中的$mount
这段代码首先缓存了原来的$mount
方法,重新定义该方法,在方法中,如果检查没有定义 render 方法,则会将 template 字符串转为 render 函数,无论单文件.vue 组件开发,还是定义 template 或者 el 属性,最终都会转成 render 函数,它是通过调用compileToFunctions
方法实现的,最后调用原来的$mount
方法挂载
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' &&
warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(
template,
{
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
},
this
)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
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
# 如果有 render 函数,则直接调用 mountComponent
原先的$mount
方法在 ./src/platform/web/runtime/index.js
定义,实际上是调用了mountComponent
方法
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
2
3
4
5
6
7
# 分析 mountComponent 函数
mountComponent
方法定义在./src/core/instance/lifecycle.js
中,将用户定义的 el 元素(id 为 app 的 DOM 元素)挂载到实例上的$el
属性
其中最主要的就是_render
和_update
方法,_render
方法将实例渲染成一个虚拟 DOM,_update
方法将虚拟 DOM 渲染为真实 DOM 元素,并且通过new Watcher
去监听生成虚拟 DOM 到真实 DOM 的过程,于是数据更新之后会自动重新渲染
function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
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
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
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)
}
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// mounted被调用,用于在它的插入钩子函数中渲染创建的子组件
// 也就是说,在它的插入钩子函数中,会创建子组件并mounted子组件,组件的mounted钩子函数就在插入函数中执行的
// vm.$vode == null 说明这不是组件的初始化过程,而是通过调用外部new Vue初始化过程
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
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
# beforeMount & mounted
两个钩子函数的调用时机是在 mountComponent
函数中,定义在 src/core/instance/lifecycle.js
中
beforeMount
执行的时候,还没有 VNode,执行完beforeMount
后执行vm._render()
得到 VNode,在执行完vm._update()
把 VNode 通过patch
变成真实 DOM 后,执行Mounted
# new Vue 过程(实例化 Vue 过程)
# mergeOptions(合并 options)
将当前 vm 的 options 跟 vm.constructor 的 options 合并
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
// 函数组件?
if (typeof child === 'function') {
child = child.options
}
// 格式化props
// 格式化inject
// 格式化directives
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField(key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
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
# initLifecycle(初始化生命周期)
初始化一些基础的属性,包括$parent,$root,$children,$refs,_watcher 等等
export function initLifecycle(vm: Component) {
const options = vm.$options
// locate first non-abstract 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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# initEvents(初始化父组件的监听事件)
export function initEvents(vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners) //见下个函数
}
}
export function updateComponentListeners(
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, vm) //见下个函数
}
export function updateListeners(
on: Object,
oldOn: Object,
add: Function,
remove: Function,
vm: Component
) {
let name, cur, old, event
for (name in on) {
cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' &&
warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur)
}
add(event.name, cur, event.once, event.capture, event.passive) //见下个函数
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture) //见下个函数
}
}
}
function add(event, fn, once) {
if (once) {
target.$once(event, fn)
} else {
target.$on(event, fn)
}
}
function remove(event, fn) {
target.$off(event, fn)
}
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
# initRender(初始化渲染函数)
export function initRender(vm: Component) {
vm._vnode = null // the root of the child tree
const options = vm.$options
const parentVnode = (vm.$vnode = options._parentVnode) // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) //见下个函数
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) //见下个函数
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(
vm,
'$attrs',
(parentData && parentData.attrs) || emptyObject,
() => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
},
true
)
defineReactive(
vm,
'$listeners',
options._parentListeners || emptyObject,
() => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
},
true
)
} else {
defineReactive(
vm,
'$attrs',
(parentData && parentData.attrs) || emptyObject,
null,
true
)
defineReactive(
vm,
'$listeners',
options._parentListeners || emptyObject,
null,
true
)
}
}
export function createElement(
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType) //见下个函数
}
export function _createElement(
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' &&
warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(
data
)}\n` + 'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode() //创建空节点
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode() //创建空节点
}
// warn against non-primitive key
if (
process.env.NODE_ENV !== 'production' &&
isDef(data) &&
isDef(data.key) &&
!isPrimitive(data.key)
) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
// support single function children as default scoped slot
if (Array.isArray(children) && typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context
) //平台内置元素
} else if (
isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
) {
// component
vnode = createComponent(Ctor, data, context, children, tag) //递归创建组件,是递归树的重要函数
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(tag, data, children, undefined, undefined, context)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (isDef(vnode)) {
if (ns) applyNS(vnode, ns)
return vnode
} else {
return createEmptyVNode()
}
}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# beforeCreate(钩子函数)
调用用户定义的 beforeCreate 函数,这个时候还没有执行initState
,因此无法访问 data,props,methods,watch,computed 等属性
# initInjections(初始化 injections)
export function initInjections(vm: Component) {
const result = resolveInject(vm.$options.inject, vm) //见下个函数
if (result) {
observerState.shouldConvert = false
Object.keys(result).forEach((key) => {
/* istanbul ignore else */
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])
}
})
observerState.shouldConvert = true
}
}
export function resolveInject(inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject).filter((key) => {
/* istanbul ignore next */
return 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
while (source) {
if (source._provided && provideKey in source._provided) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
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
}
}
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
# initState(初始化 state)
最先初始化 props,因此在 data,methods,watch,computed 中都可以访问 props
然后初始化 methods,因此在 data,computed,watch 中可以访问 methods,
然后初始化 data,因此在 computed,watch 中可以访问 data,说明一下 methods 中可以访问 data 的原因,methods 中只是函数声明和定义,不会立即访问 data 中属性,只是在执行的时候才会访问,因此这是没毛病的
然后初始化 computed,最后初始化 watch
# 1. initProps
function initProps(vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = (vm._props = {})
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = (vm.$options._propKeys = [])
const isRoot = !vm.$parent
// root instance props should be converted
observerState.shouldConvert = isRoot
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
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
)
}
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)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
observerState.shouldConvert = true
}
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
# 2. initMethods
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(`Method "${key}" has already been defined as a prop.`, 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 $.`
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3. initData
function initData(vm: Component) {
let data = vm.$options.data
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
)
}
// proxy data on instance
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]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
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
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
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
# 4. initComputed
function initComputed(vm: Component, computed: Object) {
const watchers = (vm._computedWatchers = Object.create(null))
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
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) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
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)
}
}
}
}
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
# 5. initWatch
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)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
# initProvide(初始化 provide)
export function initProvide(vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function' ? provide.call(vm) : provide
}
}
2
3
4
5
6
# created(钩子函数)
此时执行了initState
函数,props,data,methods,watch,computed 等属性都可以访问了
# 调用生命周期函数
export function callHook(vm: Component, hook: string) {
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`)
}
}
}
if (vm._hasHookEvent) {
//hook,一个vue内部暴露给外部的钩子,用法 @hook:updated=handler
vm.$emit('hook:' + hook)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 响应式理解
首先,我们会在 data 中写一些数据,像这样
new Vue({
data() {
return {
a: 'haha',
b: 'xixi'
}
}
})
2
3
4
5
6
7
8
然后当 Vue 执行构造函数的时候,会通过 observe 将 a 和 b 属性变成响应式数据
变成响应式数据后,每当我们访问 a 或者 b 的时候,a 或者 b 就会各自 new 一个 dep 实例,然后通过 dep 收集依赖,依赖其实指的就是 watcher 实例,注意,当收集依赖的时候,上下文只有一个 watcher 实例(如果是 compile 函数编译 template 模板的时候,则为 render watcher),当我们修改 a 或者 b 属性的时候,就会通知依赖,也就是 watcher 去更新
接下来执行$mount 函数的时候,会走到下面这一步
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
},
true /* isRenderWatcher */
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
也就是说,在得到 VNode 和挂载真实 DOM 的过程中,会实例化一个 watcher,这个就是 render watcher
这个过程中会访问到 vue 实例上的属性,也就是 a 或者 b,这个时候,开始收集依赖,也就是收集 render watcher 实例到 a 属性上的 dep 实例中的 subs 数组中,同时 render watcher 也会通过实例的 deps 数组来记录自己被该 dep 实例收集了,这个过程其实就是 render watcher 订阅了 a 属性的 dep,每当数据改变的时候,dep 会通知 subs 数组中的每一个依赖去更新
computed 解析
new Vue({
data() {
return {
a: 'haha',
b: 'xixi'
}
},
computed: {
c() {
return this.a + this.b
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
c 属性其实就是 computed watcher,computed watcher 并没有马上执行 getter 获取值,而是先实例化一个 dep,当在 template 模板中像访问 data 属性一样访问 c 属性,会收集 render watcher 实例到 c 属性上的 dep 实例中的 subs 数组中,同时 render watcher 也会通过实例的 deps 数组来记录自己被该 dep 实例收集了,这个过程其实就是 render watcher 订阅了 c 属性的 dep,每当数据改变的时候,dep 会通知 subs 数组中的 render watcher 依赖去更新
接下来,Dep.target 变成了 computed watcher,执行 c 的 getter 函数,这时候会访问到响应式数据 a 和 b,于是会收集 computed watcher 到 a 和 b 各自的 dep 实例中的 subs 数组,同时 computed watcher 也会通过实例的 deps 数组来记录自己被 a 和 b 的 dep 实例收集了,每当 a 或者 b 数据改变的时候,a 或者 b 中的 dep 会通知 subs 数组中的 computed watcher 依赖去更新,这个时候其实会先比较计算后的新值和旧值是否一样,不一样的话,computed watcher 会通过 c 属性的 dep 去通知 subs 数组中的 render watcher 去更新
# 组件化
# createComponent
Vue 调用$mount
的时候,会调用_render
将实例渲染为虚拟 DOM,_render
中主要是调用 render 函数(用户定义或者模版编译生成的),render 函数最终是调用 createElement 函数
createElement 函数定义在./src/core/vdom/create-element.js
中,最终通过new VNode()
生成虚拟 DOM
注意 createElement 中有一个逻辑是对参数 tag 的判断,如果是一个普通的 HTML 标签,则会实例化一个普通 VNode 节点,否则通过createComponent
方法创建一个组件 VNode(也就是走到 else 逻辑)
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context
)
} else if (
isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(tag, data, children, undefined, undefined, context)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
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
createComponent
方法定义在./src/core/vdom/create-component.js
中
function createComponent(
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// merge component management hooks onto the placeholder node
mergeHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
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
可以看到,createComponent
的逻辑也会有一些复杂,但是分析源码比较推荐的是只分析核心流程,分支流程可以之后针对性的看,所以这里针对组件渲染这个 case 主要就 3 个关键步骤:
# 1. 构造子类构造函数
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
2
3
4
5
6
_base
是什么?从哪里来?
在 src/core/global-api/index.js
中的 initGlobalAPI
函数有这么一段逻辑:Vue.options._base = Vue
将 Vue 构造函数挂载到 options 上,然后在 src/core/instance/init.js
里 Vue 原型上的 _init
函数(new 的时候执行)中有这么一段逻辑:
//把Vue构造函数的options和用户传入的options做一层合并挂载到实例上
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
2
3
4
5
6
Vue.extend
?
在了解了 baseCtor
指向了 Vue 之后,接下来看一下 Vue.extend
函数的定义,在 src/core/global-api/extend.js
中。
function initExtend(Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function(extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production') {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' +
name +
'". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
}
const Sub = function VueComponent(options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(Super.options, extendOptions)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function(type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
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
Vue.extend
的作用就是构造一个 Vue
的子类,它使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 Vue
的构造器 Sub
并返回,然后对 Sub
这个对象本身扩展了一些属性,如扩展 options
、添加全局 API 等;并且对配置中的 props
和 computed
做了初始化工作;最后对于这个 Sub
构造函数做了缓存,避免多次执行 Vue.extend
的时候对同一个子组件重复构造。
这样当我们去实例化 Sub
的时候,就会执行 this._init
逻辑再次走到了 Vue
实例的初始化逻辑,实例化子组件的逻辑在之后介绍。
const Sub = function VueComponent(options) {
this._init(options)
}
2
3
# 2. 安装组件钩子函数
// merge component management hooks onto the placeholder node
mergeHooks(data)
const hooksToMerge = Object.keys(componentVNodeHooks)
function mergeHooks(data: VNodeData) {
if (!data.hook) {
data.hook = {}
}
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const fromParent = data.hook[key]
const ours = componentVNodeHooks[key]
data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
}
}
function mergeHook(one: Function, two: Function): Function {
return function(a, b, c, d) {
one(a, b, c, d)
two(a, b, c, d)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
整个 mergeHooks
的过程就是把 componentVNodeHooks
的钩子函数合并到 data.hook
中,在 VNode 执行 patch
的过程中执行相关的钩子函数,具体的执行稍后在介绍 patch
过程中会详细介绍。这里要注意的是合并策略,在合并过程中,如果某个时机的钩子已经存在 data.hook
中,那么通过执行 mergeHook
函数做合并,这个逻辑很简单,就是在最终执行的时候,依次执行这两个钩子函数即可
# 3. 实例化 vnode
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
2
3
4
5
6
7
8
9
10
11
12
13
最后一步通过 new VNode
实例化一个 vnode
并返回。需要注意的是和普通元素节点的 vnode
不同,组件的 vnode
是没有 children
的,这点很关键,在之后的 patch
过程中会再提及
# 总结
上面我们分析了 createComponent
的实现,了解到它在渲染一个组件的时候的 3 个关键逻辑:构造子类构造函数,安装组件钩子函数和实例化 vnode
。createComponent
后返回的是组件 vnode
,它也一样走到 vm._update
方法,进而执行了 patch
函数,
# patch
通过上面的分析可以知道,当通过 createComponent
创建了组件 VNode,接下来会走到 vm._update
,执行 vm.__patch__
去把 VNode 转换成真正的 DOM 节点。这里分析一下组件 VNode 和普通元素 VNode 的有什么不同
# createElm(创建真实 DOM)
patch 的过程会调用 createElm
创建元素节点,回顾一下 createElm
的实现,它的定义在 src/core/vdom/patch.js
中:
function createElm(
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// ...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# createComponent(定义在 createPatchFunction 内部的函数,跟上面不同)
这里删掉多余的代码,只保留关键的逻辑,这里会判断 createComponent(vnode, insertedVnodeQueue, parentElm, refElm)
的返回值,如果为 true
则直接结束,那么接下来看一下 createComponent
方法的实现:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
createComponent
函数中,首先对 vnode.data
做了一些判断:
let i = vnode.data
if (isDef(i)) {
// ...
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */)
// ...
}
// ..
}
2
3
4
5
6
7
8
9
# init
如果 vnode
是一个组件 VNode,那么条件会满足,并且得到 i
就是 init
钩子函数,在创建组件 VNode 的时候合并钩子函数中就包含 init
钩子函数,定义在 src/core/vdom/create-component.js
中:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
init
钩子函数执行也很简单,先不考虑 keepAlive
的情况,它是通过 createComponentInstanceForVnode
创建一个 Vue 的实例,然后调用 $mount
方法挂载子组件
# createComponentInstanceForVnode(创建组件实例)
先来看一下 createComponentInstanceForVnode
的实现:
export function createComponentInstanceForVnode(
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
return new vnode.componentOptions.Ctor(options)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
createComponentInstanceForVnode
函数构造的一个内部组件的参数,然后执行 new vnode.componentOptions.Ctor(options)
。这里的 vnode.componentOptions.Ctor
对应的就是子组件的构造函数,我们上一节分析了它实际上是继承于 Vue 的一个构造器 Sub
,相当于 new Sub(options)
这里有几个关键参数要注意几个点,_isComponent
为 true
表示它是一个组件,parent
表示当前激活的组件实例(注意,这里比较有意思的是如何拿到组件实例,后面会介绍。
# new 的时候执行_init
方法
所以子组件的实例化实际上就是在这个时机执行的,并且它会执行实例的 _init
方法,这个过程有一些和之前不同的地方需要挑出来说,代码在 src/core/instance/init.js
中:
Vue.prototype._init = function(options?: Object) {
const vm: Component = this
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// ...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这里首先是合并 options
的过程有变化,_isComponent
为 true,所以走到了 initInternalComponent
过程
# initInternalComponent(初始化内部组件)
这个函数的实现也简单看一下:
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
const opts = (vm.$options = Object.create(vm.constructor.options))
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这个过程我们重点记住以下几个点即可:opts.parent = options.parent
、opts._parentVnode = parentVnode
,它们是把之前我们通过 createComponentInstanceForVnode
函数传入的几个参数合并到内部的选项 $options
里了。
再来看一下 _init
函数最后执行的代码:
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
2
3
# _render(得到 VNode)
由于组件初始化的时候是不传 el 的,所以上面代码不会执行,因此组件是自己接管了 $mount
的过程,这个过程的主要流程在上一章介绍过了,回到组件 init
的过程,componentVNodeHooks
的 init
钩子函数,在完成实例化的 _init
后,接着会执行 child.$mount(hydrating ? vnode.elm : undefined, hydrating)
。这里 hydrating
为 true 一般是服务端渲染的情况,我们只考虑客户端渲染,所以这里 $mount
相当于执行 child.$mount(undefined, false)
,它最终会调用 mountComponent
方法,进而执行 vm._render()
方法:
Vue.prototype._render = function(): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
// ...
}
// set parent
vnode.parent = _parentVnode
return vnode
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
我们只保留关键部分的代码,这里的 _parentVnode
就是当前组件的父 VNode,而 render
函数生成的 vnode
当前组件的渲染 vnode
,vnode
的 parent
指向了 _parentVnode
,也就是 vm.$vnode
,它们是一种父子的关系。
# _update(根据 VNode 渲染真实 DOM)
我们知道在执行完 vm._render
生成 VNode 后,接下来就要执行 vm._update
去渲染 VNode 了。来看一下组件渲染的过程中有哪些需要注意的,vm._update
的定义在 src/core/instance/lifecycle.js
中:
export let activeInstance: any = null
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__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
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
_update
过程中有几个关键的代码,首先 vm._vnode = vnode
的逻辑,这个 vnode
是通过 vm._render()
返回的组件渲染 VNode,vm._vnode
和 vm.$vnode
的关系就是一种父子关系,用代码表达就是 vm._vnode.parent === vm.$vnode
。
# activeInstance(保存当前上下文的 Vue 实例)
还有一段比较有意思的代码:
export let activeInstance: any = null
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
// ...
const prevActiveInstance = activeInstance
activeInstance = vm
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个 activeInstance
作用就是保持当前上下文的 Vue 实例,它是在 lifecycle
模块的全局变量,定义是 export let activeInstance: any = null
,并且在之前我们调用 createComponentInstanceForVnode
方法的时候从 lifecycle
模块获取,并且作为参数传入的。因为实际上 JavaScript 是一个单线程,Vue 整个初始化是一个深度遍历的过程,在实例化子组件的过程中,它需要知道当前上下文的 Vue 实例是什么,并把它作为子组件的父 Vue 实例。之前我们提到过对子组件的实例化过程先会调用 initInternalComponent(vm, options)
合并 options
,把 parent
存储在 vm.$options
中,在 $mount
之前会调用 initLifecycle(vm)
方法:
export function initLifecycle(vm: Component) {
const options = vm.$options
// locate first non-abstract 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
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以看到 vm.$parent
就是用来保留当前 vm
的父实例,并且通过 parent.$children.push(vm)
来把当前的 vm
存储到父实例的 $children
中。
在 vm._update
的过程中,把当前的 vm
赋值给 activeInstance
,同时通过 const prevActiveInstance = activeInstance
用 prevActiveInstance
保留上一次的 activeInstance
。实际上,prevActiveInstance
和当前的 vm
是一个父子关系,当一个 vm
实例完成它的所有子树的 patch 或者 update 过程后,activeInstance
会回到它的父实例,这样就完美地保证了 createComponentInstanceForVnode
整个深度遍历过程中,我们在实例化子组件的时候能传入当前子组件的父 Vue 实例,并在 _init
的过程中,通过 vm.$parent
把这个父子关系保留。
那么回到 _update
,最后就是调用 __patch__
渲染 VNode 了。
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
function patch(oldVnode, vnode, hydrating, removeOnly) {
// ...
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
// ...
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里又回到了本节开始的过程,之前分析过负责渲染成 DOM 的函数是 createElm
,注意这里我们只传了 2 个参数,所以对应的 parentElm
是 undefined
。我们再来看看它的定义:
function createElm(
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// ...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// ...
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// ...
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
// ...
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
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
注意,这里我们传入的 vnode
是组件渲染的 vnode
,也就是我们之前说的 vm._vnode
,如果组件的根节点是个普通元素,那么 vm._vnode
也是普通的 vnode
,这里 createComponent(vnode, insertedVnodeQueue, parentElm, refElm)
的返回值是 false。接下来的过程就和我们上一章一样了,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm
,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复本节开始的过程,这样通过一个递归的方式就可以完整地构建了整个组件树。
由于我们这个时候传入的 parentElm
是空,所以对组件的插入,在 createComponent
有这么一段逻辑:
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
// ....
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */)
}
// ...
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在完成组件的整个 patch
过程后,最后执行 insert(parentElm, vnode.elm, refElm)
完成组件的 DOM 插入,如果组件 patch
过程中又创建了子组件,那么 DOM 的插入顺序是先子后父。
# 总结
那么到此,一个组件的 VNode 是如何创建、初始化、渲染的过程也就介绍完毕了。
# vue2.x 中的工具函数
# 判断浏览器环境的代码
./src/core/util/env.js
const inBrowser = typeof window !== 'undefined'
const UA = inBrowser && window.navigator.userAgent.toLowerCase()
const isIE = UA && /msie|trident/.test(UA)
const isIE9 = UA && UA.indexOf('msie 9.0') > 0
const isEdge = UA && UA.indexOf('edge/') > 0
const isAndroid = UA && UA.indexOf('android') > 0
const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
2
3
4
5
6
7
8
# this._props[key]
变成this[key]
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# API 理解
# $set
./src/core/observer/index.js
目的:添加属性时手动变成响应式,并触发依赖更新
Vue.prototype.$set = set
function set(target: Array<any> | Object, key: any, val: any): any {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (hasOwn(target, key)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
/*_isVue判断是否vue实例
vmCount判断target是不是根数据
*/
process.env.NODE_ENV !== 'production' &&
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
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
# $delete
./src/core/observer/index.js
目的:删除属性后主动触发依赖更新
Vue.prototype.$delete = del
del (target: Array<any> | Object, key: any) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# $nextTick
./src/core/util/env.js
事件循环:当宏任务执行完后,执行微任务,微任务执行完,开始新一轮的宏任务
属于微任务的事件包括但不限于以下几种:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
属于宏任务的事件包括但不限于以下几种:
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requestAnimationFrame
- I/O
- UI 交互事件
nextTick 作用:往微任务队列中注册一个微任务
注意:vue 是异步更新 DOM 的,当数据修改的时候,会将异步更新 DOM 加入任务队列,异步更新 DOM 也是通过 nextTick 来加入任务队列的
//下面这种情况是将异步更新DOM加入微任务队列,再将nextTick注册的回调加入微任务队列
this.foo = 'abc'
this.$nextTick(function() {
//DOM更新了
})
//下面这种情况是将nextTick注册的回调加入微任务队列,再将异步更新DOM加入微任务队列
this.$nextTick(function() {
//DOM未更新
})
this.foo = 'abc'
2
3
4
5
6
7
8
9
10
11
nextTick 依次检查 setTmmediate(宏任务),MessageChannel,Promise(微任务),setTimout(宏任务)
用 pending 变量来控制重复调用 nextTick,第一次调用 nextTick 将回调函数加入到 callback 队列中,并且加入到任务队列,第二次调用 nextTick,会将回调函数继续加入到 callback 队列中,由于 pending 为 true 则不会重复加入任务队列,当执行任务队列的回调时,将 pending 设为 false,并依次执行 callback 队列中的函数,这样避免了重复加入任务队列
Vue.prototype.$nextTick = function(fn: Function) {
return nextTick(fn, this)
}
const nextTick = (function() {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler() {
//依次执行回调函数并清空回调函数队列
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// An asynchronous deferring mechanism.
// In pre 2.4, we used to use microtasks (Promise/MutationObserver)
// but microtasks actually has too high a priority and fires in between
// supposedly sequential events (e.g. #4521, #6690) or even between
// bubbling of the same event (#6566). Technically setImmediate should be
// the ideal choice, but it's not available everywhere; and the only polyfill
// that consistently queues the callback after all DOM events triggered in the
// same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(nextTickHandler)
}
} else if (
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = nextTickHandler
timerFunc = () => {
port.postMessage(1)
}
} else if (typeof Promise !== 'undefined' && isNative(Promise)) {
/* istanbul ignore next */
// use microtask in non-DOM environments, e.g. Weex
const p = Promise.resolve()
timerFunc = () => {
p.then(nextTickHandler)
}
} else {
// fallback to setTimeout
//这里降级为宏任务,比微任务稍后执行
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick(cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
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
# $watch
./src/core/instance/state.js
目的:监听一个属性,返回一个(取消监听的)函数(unwatchFn)
Vue.prototype.$watch = function(
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
//如果回调函数是一个对象,监听该对象
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
//是否立即执行回调
cb.call(vm, watcher.value)
}
return function unwatchFn() {
watcher.teardown()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# createWatcher
./src/core/instance/state.js
目的:通过$watch 去监听一个属性,返回一个(取消监听的)函数(unwatchFn)
function createWatcher(
vm: Component,
keyOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(keyOrFn, handler, options)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# class Watcher(核心)
./src/core/observer/watcher.js
目的:在 getter 的时候收集依赖(也就是 watcher 实例),在 setter 的时候触发依赖(通过 watcher.update)
class Watcher {
vm: Component
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
active: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: ISet
newDepIds: ISet
getter: Function
value: any
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression =
process.env.NODE_ENV !== 'production' ? expOrFn.toString() : ''
// parse expression for getter
if (typeof expOrFn === 'function') {
/*
当expOrFn是函数时,它不止可以动态返回数据,其中读取的所有数据也都会被Watcher观察
也就是说,如果函数从Vue实例上读取了两个数据,那么Watcher会同时观察这两个数据的变化
当其中任意一个发生变化时,Watcher都会得到通知
事实上:计算属性(Computed)的实现原理与expOrFn支持函数有很大的关系
*/
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function() {}
}
}
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
//递归读取属性收集依赖
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps() {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate() {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown() {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# parsePath(getter)
./src/core/util/lang.js
目的:返回一个读取属性的函数,目的是为了触发响应式数据的 getter
function parsePath(path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function(obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# traverse(deep)
./src/core/observer/watcher.js
目的:循环遍历 value,实现深度监听属性
function traverse(val: any) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse(val: any, seen: ISet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
/*
注意这里的val[i]会触发getter收集依赖
因此traverse必须在window.target=undefined之前执行
这样内部属性才能收集到当前watcher实例,之后内部属性发生改变就会触发父级属性的watcher
*/
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
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
# observe
./src/core/observer/index.js
目的:将传入的数据变成响应式数据
function observe(value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
//判断是否已经是响应式了
ob = value.__ob__
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
//不是响应式则变成响应式数据
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# class Observer
./src/core/observer/index.js
目的:将数据变成响应式数据(注意数组和对象的区别)
class Observer {
value: any
dep: Dep
vmCount: number // number of vms that has this object as root $data
constructor(value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
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
# defineReactive
./src/core/observer/index.js
目的:将数据变成响应式,getter 时收集依赖,setter 时触发依赖
function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
//如果赋值过程出现对象赋值,这时候childOb就起作用了
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
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
# dependArray(收集数组的依赖)
./src/core/observer/index.js
目的:遍历数组每一项收集依赖
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
2
3
4
5
6
7
8
9
# arrayMethods(重写数组方法)
./src/core/observer/array.js
const arrayMethods = Object.create(arrayProto)
/**
* Intercept mutating methods and emit events
*/
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(
function(method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
}
)
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
# protoAugment(浏览器支持__proto__
时调用)
./src/core/observer/index.js
目的:将重写的数组方法挂载到属性的原型属性上,起到拦截原数组方法的作用
function protoAugment(target, src: Object, keys: any) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
2
3
4
5
# copyAugment(浏览器不支持__proto__
时调用)
./src/core/observer/index.js
目的:将方法直接挂载到属性上
function copyAugment(target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
2
3
4
5
6