Vue 2 源码解读

目录结构

Vue 2的源码采用典型的模块化结构,主要包含以下核心模块:

  • core: 核心代码,包含响应式系统、虚拟DOM、组件系统等
  • platforms: 平台特定代码,包括Web和Weex
  • compiler: 模板编译器,将模板转换为渲染函数
  • server: 服务端渲染相关代码
  • sfc: 单文件组件(.vue文件)解析器
  • shared: 共享工具函数

一、响应式系统

Vue 2的响应式系统是整个框架的核心,它基于Object.defineProperty实现数据劫持和发布订阅模式。

1.1 数据响应式原理

Observer

Observer类负责将普通JavaScript对象转换为响应式对象:

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
// src/core/observer/index.js (简化版)
export class Observer {
constructor (value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0

// 在对象上添加__ob__属性,指向Observer实例
def(value, '__ob__', this)

if (Array.isArray(value)) {
// 数组响应式处理
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
// 对象响应式处理
this.walk(value)
}
}

// 遍历对象的所有属性并将它们转换为getter/setter
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}

// 遍历数组的每一项并将它们转换为响应式
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

// 将对象转换为响应式
export function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}

defineReactive

defineReactive函数使用Object.defineProperty将对象属性转换为getter/setter:

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
// src/core/observer/index.js (简化版)
export function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
// 为每个属性创建一个依赖收集器
const dep = new Dep()

const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// 缓存原来的getter和setter
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

// 递归观察子属性
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
// 值没有变化则不触发更新
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值需要重新观察
childOb = !shallow && observe(newVal)
// 通知依赖更新
dep.notify()
}
})
}

1.2 依赖收集与更新

Dep

Dep类是一个发布订阅中心,负责收集依赖和通知更新:

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
// src/core/observer/dep.js (简化版)
export default class Dep {
static target
id
subs

constructor () {
this.id = uid++
this.subs = []
}

// 添加订阅者
addSub (sub) {
this.subs.push(sub)
}

// 移除订阅者
removeSub (sub) {
remove(this.subs, sub)
}

// 收集依赖
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}

// 通知所有订阅者更新
notify () {
// 稳定订阅者列表
const subs = this.subs.slice()
// 通知所有订阅者
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}

// 当前正在评估的Watcher
Dep.target = null
const targetStack = []

// 设置当前Watcher
export function pushTarget (target) {
targetStack.push(target)
Dep.target = target
}

// 恢复之前的Watcher
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}

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
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
// src/core/observer/watcher.js (简化版)
export default class Watcher {
constructor (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)

// 选项
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} 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()
: ''

// 解析表达式
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
}
}

this.value = this.lazy
? undefined
: this.get()
}

// 获取值并收集依赖
get () {
pushTarget(this)
let value
const vm = this.vm
try {
// 执行getter,触发依赖收集
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// 处理深度监听
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}

// 添加依赖
addDep (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)
}
}
}

// 清理依赖
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
}

// 更新
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}

// 执行更新
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
// 设置新值
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 () {
this.value = this.get()
this.dirty = false
}

// 收集所有依赖
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}

// 销毁
teardown () {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}

1.3 数组响应式处理

Vue 2对数组的变异方法进行了特殊处理,以实现数组的响应式:

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
// src/core/observer/array.js (简化版)
import { def } from '../util/index'

const arrayProto = Array.prototype
// 创建一个对象,该对象的原型指向Array.prototype
export const arrayMethods = Object.create(arrayProto)

// 需要拦截的数组方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

// 拦截数组方法
methodsToPatch.forEach(function (method) {
// 缓存原始方法
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 调用原始方法
const result = original.apply(this, args)
// 获取Observer实例
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)
// 通知更新
ob.dep.notify()
return result
})
})

二、组件系统与渲染机制

Vue 2的组件系统是构建用户界面的核心,它基于虚拟DOM实现高效的渲染。

2.1 Vue实例创建

Vue实例的创建过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/core/instance/index.js (简化版)
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}

// 混入核心方法
initMixin(Vue) // 实现 _init 方法
stateMixin(Vue) // 实现 $data, $props, $set, $delete, $watch 等
eventsMixin(Vue) // 实现 $on, $once, $off, $emit 等
lifecycleMixin(Vue) // 实现 _update, $forceUpdate, $destroy 等
renderMixin(Vue) // 实现 $nextTick, _render 等

export default Vue

_init方法

_init方法是Vue实例初始化的入口:

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
// src/core/instance/init.js (简化版)
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this

// 合并选项
if (options && options._isComponent) {
// 优化内部组件实例化
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}

// 初始化生命周期
initLifecycle(vm)

// 初始化事件
initEvents(vm)

// 初始化渲染
initRender(vm)

// 调用beforeCreate钩子
callHook(vm, 'beforeCreate')

// 初始化注入内容
initInjections(vm)

// 初始化状态
initState(vm)

// 初始化提供内容
initProvide(vm)

// 调用created钩子
callHook(vm, 'created')

// 如果有el选项,自动挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}

2.2 虚拟DOM (VNode)

VNode是Vue中虚拟DOM节点的表示:

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
// src/core/vdom/vnode.js (简化版)
export default class VNode {
tag: string | void
data: VNodeData | void
children: ?Array<VNode>
text: string | void
elm: Node | void
ns: string | void
context: Component | void
key: string | number | void
componentOptions: VNodeComponentOptions | void
componentInstance: Component | void
parent: VNode | void

// 以下是一些标志位
raw: boolean
isStatic: boolean
isRootInsert: boolean
isComment: boolean
isCloned: boolean
isOnce: boolean
asyncFactory: Function | void
asyncMeta: Object | void
isAsyncPlaceholder: boolean
ssrContext: Object | void
fnContext: Component | void
fnOptions: ?ComponentOptions
fnScopeId: ?string

constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}

// 用于调试
get child (): Component | void {
return this.componentInstance
}
}

2.3 渲染与更新

Vue 2的渲染和更新过程主要涉及_render_update方法:

_render方法

_render方法用于生成虚拟DOM:

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
// src/core/instance/render.js (简化版)
Vue.prototype._render = function () {
const vm = this
const { render, _parentVnode } = vm.$options

// 设置父VNode
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}

// 设置父节点
vm.$vnode = _parentVnode

// 渲染VNode
let vnode
try {
// 调用render函数生成VNode
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// 渲染错误时的处理
vnode = vm._vnode
} finally {
currentRenderingInstance = null
}

// 如果返回数组,则创建一个Fragment节点
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}

// 如果render函数出错,创建空VNode
if (!(vnode instanceof VNode)) {
if (Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}

// 设置父节点
vnode.parent = _parentVnode
return vnode
}

_update方法

_update方法用于将虚拟DOM渲染为真实DOM:

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
// src/core/instance/lifecycle.js (简化版)
Vue.prototype._update = function (vnode, hydrating) {
const vm = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)

vm._vnode = vnode

// 首次渲染
if (!prevVnode) {
// 初始渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}

restoreActiveInstance()

// 更新__vue__引用
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}

// 如果父节点是HOC,也更新其$el
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
}

2.4 Patch算法

Patch算法是Vue 2中的核心Diff算法,用于高效更新DOM:

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
// src/core/vdom/patch.js (简化版)
function patch (oldVnode, vnode, hydrating, removeOnly) {
// 如果新VNode不存在但旧VNode存在,则销毁旧VNode
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}

let isInitialPatch = false
const insertedVnodeQueue = []

// 如果旧VNode不存在,则创建新元素
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
// 判断是否为真实DOM
const isRealElement = isDef(oldVnode.nodeType)

// 不是真实DOM且是相同类型的VNode
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 更新已存在的节点
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// 是真实DOM,创建空的VNode
if (isRealElement) {
// 挂载到真实元素上
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
}
}
// 不是服务端渲染或混合失败,创建空VNode
oldVnode = emptyNodeAt(oldVnode)
}

// 替换已存在的元素
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)

// 创建新节点
createElm(
vnode,
insertedVnodeQueue,
parentElm,
nodeOps.nextSibling(oldElm)
)

// 销毁旧节点
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}

invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}

patchVnode

patchVnode函数用于更新已存在的节点:

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
// src/core/vdom/patch.js (简化版)
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
// 如果新旧VNode相同,直接返回
if (oldVnode === vnode) {
return
}

// 复用DOM元素
const elm = vnode.elm = oldVnode.elm

// 异步占位符
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}

// 静态节点优化
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}

// 执行组件prepatch钩子
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}

// 获取子节点
const oldCh = oldVnode.children
const ch = vnode.children

// 执行更新钩子
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}

// 如果新VNode没有文本
if (isUndef(vnode.text)) {
// 如果新旧VNode都有子节点
if (isDef(oldCh) && isDef(ch)) {
// 更新子节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
// 如果只有新VNode有子节点
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// 如果只有旧VNode有子节点
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
// 如果旧VNode有文本,清空
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
// 如果文本不同,更新文本
nodeOps.setTextContent(elm, vnode.text)
}

// 执行postpatch钩子
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}

updateChildren

updateChildren函数是Vue 2中的核心Diff算法,用于高效更新子节点:

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
// src/core/vdom/patch.js (简化版)
function updateChildren (
parentElm,
oldCh,
newCh,
insertedVnodeQueue,
removeOnly
) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm

// 是否是简单移动
const canMove = !removeOnly

// 双端比较算法
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
// 旧起始节点为空,移动指针
oldStartVnode = oldCh[++oldStartIdx]
} else if (isUndef(oldEndVnode)) {
// 旧结束节点为空,移动指针
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// 旧起始和新起始节点相同
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// 旧结束和新结束节点相同
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// 旧起始和新结束节点相同
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
// 将旧起始节点移动到最后
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// 旧结束和新起始节点相同
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// 将旧结束节点移动到最前
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// 以上四种情况都不满足
// 创建key到索引的映射
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// 在旧子节点中查找新起始节点的位置
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

// 如果没找到,创建新元素
if (isUndef(idxInOld)) {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// 找到了,获取要移动的节点
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
// 节点相同,进行patch
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
// 移动节点
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// 相同key但不是相同节点,创建新元素
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}

// 处理剩余节点
if (oldStartIdx > oldEndIdx) {
// 旧子节点处理完,添加剩余的新子节点
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
// 新子节点处理完,移除剩余的旧子节点
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}

三、生命周期与指令系统

3.1 生命周期钩子

Vue 2的生命周期钩子是在特定阶段调用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/core/instance/lifecycle.js (简化版)
export function callHook (vm, hook) {
// 禁用依赖收集
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}

生命周期钩子的调用时机:

  1. beforeCreate: 在实例初始化之后,数据观测和事件配置之前
  2. created: 在实例创建完成后,数据观测、属性和方法的运算,watch/event事件回调已完成
  3. beforeMount: 在挂载开始之前被调用
  4. mounted: 在挂载完成后调用
  5. beforeUpdate: 数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前
  6. updated: 数据更改导致的虚拟DOM重新渲染和打补丁之后调用
  7. beforeDestroy: 实例销毁之前调用
  8. destroyed: 实例销毁后调用

3.2 指令系统

Vue 2的指令系统允许开发者扩展HTML的功能:

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
// src/core/vdom/modules/directives.js (简化版)
export default {
create: updateDirectives,
update: updateDirectives,
destroy: function unbindDirectives (vnode) {
updateDirectives(vnode, emptyNode)
}
}

function updateDirectives (oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode)
}
}

function _update (oldVnode, vnode) {
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)

const dirsWithInsert = []
const dirsWithPostpatch = []

let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
if (!oldDir) {
// 新指令,调用bind
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else {
// 已存在的指令,调用update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}

// 处理需要inserted钩子的指令
if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}

// 处理需要componentUpdated钩子的指令
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}

// 处理需要解绑的指令
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// 不再存在的指令,调用unbind
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}

四、编译器与模板解析

Vue 2的编译器负责将模板转换为渲染函数。

4.1 编译过程

编译过程主要包括解析(parse)、优化(optimize)和生成(generate)三个阶段:

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
// src/compiler/index.js (简化版)
export function compile (
template,
options
) {
const finalOptions = Object.create(baseOptions)

// 合并选项
if (options) {
// 合并自定义模块
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// 合并指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// 复制其他选项
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}

// 编译模板
const compiled = baseCompile(template.trim(), finalOptions)

// 检查错误
if (process.env.NODE_ENV !== 'production') {
detectErrors(compiled.ast, warn)
}

compiled.errors = errors
compiled.tips = tips

return compiled
}

// 基础编译函数
function baseCompile (
template,
options
) {
// 解析模板为AST
const ast = parse(template.trim(), options)

// 优化AST
if (options.optimize !== false) {
optimize(ast, options)
}

// 生成代码
const code = generate(ast, options)

return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}

4.2 解析(Parse)

解析阶段将模板字符串转换为抽象语法树(AST):

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
// src/compiler/parser/index.js (简化版)
export function parse (
template,
options
) {
// 解析选项
const stack = []
let root
let currentParent

// HTML解析器
parseHTML(template, {
start (tag, attrs, unary, start, end) {
// 处理开始标签
const element = createASTElement(tag, attrs, currentParent)

// 处理指令
processFor(element)
processIf(element)
processOnce(element)

// 树管理
if (!root) {
root = element
}

if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else {
if (element.slotScope) {
// 作用域插槽
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
}
currentParent.children.push(element)
element.parent = currentParent
}
}

// 非自闭合标签入栈
if (!unary) {
currentParent = element
stack.push(element)
} else {
closeElement(element)
}
},

end (tag, start, end) {
// 处理结束标签
const element = stack[stack.length - 1]
// 弹出栈
stack.length -= 1
currentParent = stack[stack.length - 1]
closeElement(element)
},

chars (text, start, end) {
// 处理文本内容
if (!currentParent) {
return
}

const children = currentParent.children
if (text) {
let res
let child
if (text !== ' ' && (res = parseText(text, delimiters))) {
// 带表达式的文本
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text
}
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
// 纯文本
child = {
type: 3,
text
}
}
if (child) {
children.push(child)
}
}
},

comment (text, start, end) {
// 处理注释
if (currentParent) {
const child = {
type: 3,
text,
isComment: true
}
currentParent.children.push(child)
}
}
})

return root
}

4.3 优化(Optimize)

优化阶段标记静态节点,提高渲染性能:

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
// src/compiler/optimizer.js (简化版)
export function optimize (root, options) {
if (!root) return

// 标记静态节点
markStatic(root)
// 标记静态根节点
markStaticRoots(root, false)
}

function markStatic (node) {
// 判断节点是否静态
node.static = isStatic(node)

if (node.type === 1) {
// 不要将组件插槽内容标记为静态
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}

// 递归处理子节点
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
// 如果子节点不是静态的,父节点也不是
if (!child.static) {
node.static = false
}
}

// 处理条件节点
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}

function markStaticRoots (node, isInFor) {
if (node.type === 1) {
// 静态节点或有v-once指令
if (node.static || node.once) {
node.staticInFor = isInFor
}

// 对于有子节点且子节点不只是文本的静态节点,标记为静态根
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}

// 递归处理子节点
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}

// 处理条件节点
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}

4.4 生成(Generate)

生成阶段将AST转换为渲染函数代码:

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
// src/compiler/codegen/index.js (简化版)
export function generate (
ast,
options
) {
const state = new CodegenState(options)

// 根据AST生成代码
const code = ast ? genElement(ast, state) : '_c("div")'

return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}

export function genElement (el, state) {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}

// 处理静态根节点
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
// v-once
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
// v-for
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
// v-if
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
// 模板
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
// 插槽
return genSlot(el, state)
} else {
// 组件或元素
let code
if (el.component) {
// 组件
code = genComponent(el.component, el, state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
// 生成元素数据
data = genData(el, state)
}

const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // 数据
}${
children ? `,${children}` : '' // 子节点
})`
}

// 模块转换
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}

return code
}
}

总结

Vue 2源码的核心亮点:

  1. 响应式系统:基于Object.defineProperty的数据劫持和发布订阅模式,实现数据与视图的自动同步
  2. 虚拟DOM:通过JavaScript对象表示DOM结构,配合高效的Diff算法实现最小化DOM操作
  3. 组件系统:组件是Vue的核心抽象,提供了组合、复用和封装的能力
  4. 编译系统:将模板编译为渲染函数,支持指令、插值等特性
  5. 生命周期:提供完整的组件生命周期钩子,方便开发者在不同阶段执行逻辑

Vue 2的源码设计体现了优秀的前端框架架构思想,通过深入理解其实现原理,可以更好地使用Vue进行开发,也能学习到很多前端工程化的最佳实践。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器