# 挂载阶段
模板编译阶段完成后,就进入到了挂载阶段,从官方的生命周期中,我们可以发现此阶段涉及到的生命周期比较多,有beforeMount、mounted、beforeUpdate、updated四个。覆盖了我们日常开发中的大部分生命周期。在这个期间,Vue主要做了两件事:
- 挂载DOM:创建Vue实例并用其替换
el选项对应的DOM元素,再将模板内容渲染到视图中 - 数据监控:开启对模板中数据(状态)的监控,当数据(状态)发生变化时通知其依赖进行视图更新

回顾上节我们讲的模板编译阶段,完整版$mount最终调用了运行时的$mount,而运行时的$mount最后调用的是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
在Vue中,调用mountComponent就标志着正式进入了挂载阶段,本节我们一起来探讨mountComponent究竟做了什么。源码在src/core/instance/lifecycle.js
# 挂载DOM

挂载DOM是挂载阶段的第一件事,创建Vue实例并用其替换el选项对应的DOM元素,再将模板内容渲染到视图中。具体代码如下:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // 将组件实例$el设置为挂载的dom vm.$el = el // 如果没有render,则创建一个空的VNode作为render // 确保在挂载过程中至少有一个渲染函数可用 if (!vm.$options.render) { vm.$options.render = createEmptyVNode } // 调用生命周期 beforeMount callHook(vm, 'beforeMount') let updateComponent updateComponent = () => { // 执行渲染函数vm._render()得到一份最新的VNode节点树 // vm._update()方法对最新的VNode节点树与上一次渲染的旧VNode节点树进行对比并更新DOM节点(即patch操作),完成一次渲染。 // hydrating 是一个布尔值,用于指示是否是服务端渲染(hydration)的过程。 vm._update(vm._render(), hydrating) } }成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vm.$el = el:将组件实例的$el属性设置为传入的el,即组件要挂载的目标元素。- 检查是否存在渲染函数:
- 如果组件的配置项
vm.$options.render不存在,即没有显式定义渲染函数,则将vm.$options.render设置为一个空的 VNode(虚拟节点)。 - 这是为了确保在挂载过程中至少有一个渲染函数可用。
- 如果组件的配置项
- 调用
beforeMount生命周期钩子 - 定义
updateComponent函数:updateComponent函数是一个渲染函数,负责执行组件的渲染逻辑。- 在后续步骤中会被调用来执行初始的渲染和后续的更新。
- 调用
vm._update(vm._render(), hydrating):vm._render()执行渲染函数,生成最新的 VNode 节点树。vm._update()方法将最新的 VNode 节点树与上一次渲染的旧 VNode 节点树进行对比,并根据差异更新 DOM 节点(执行 patch 操作),完成一次渲染。- 第二个参数
hydrating是一个布尔值,用于指示是否是服务端渲染(hydration)的过程。
从上述代码可以看出,挂载DOM阶段主要做的事情为设置目标元素、检查渲染函数、执行 beforeMount 生命周期钩子以及执行渲染函数和更新 DOM。上述代码执行完毕后,我们就可以将模板内容渲染到视图中。
# 数据监控
将模板内容渲染到视图后,我们就进入了挂载阶段的第二件事,数据监控,开启对模板中数据(状态)的监控,当数据(状态)发生变化时通知其依赖进行视图更新
new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // $vnode 组件实例的占位节点,用于表示组件在父组件中的位置 // $vnode 为null,表示组件初次挂载 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
Watcher 构造函数在其他部分有讲,这里就简单介绍下。Watcher类构造函数的第二个参数支持两种类型:函数和数据路径(如a.b.c)。如果是数据路径,会根据路径去读取这个数据;如果是函数,会执行这个函数。一旦读取了数据或者执行了函数,就会触发数据或者函数内数据的getter方法,而在getter方法中会将Watcher实例添加到该数据的依赖列表中,当该数据发生变化时就会通知依赖列表中所有的依赖,依赖接收到通知后就会调用第四个参数回调函数去更新视图。
在这个函数中,将updateComponent作为Watcher第二个参数传入,创建一个watcher实例。并且会立即执行updateComponent, 完成渲染。
再执行后续代码,使用$vnode判断是否为初次渲染,如果是,则将私有属性_isMounted改为true,表示已经初始化过。此时再调用 mounted 生命周期钩子。
Watcher实例化后,updateComponent函数中使用到的所有数据都将被watcher监听,一但发生改变,就执行第四个参数,也就是这里的before, 又通过私有属性_isMounted判断是否渲染完成后,再次调用beforeUpdate生命周期钩子。
Watcher内部会在更新完成后调用updated生命周期钩子,从而完成整个挂载阶段的流程。
# 总结
挂载阶段是Vue组件的生命周期的第三部分。在挂载阶段,Vue完成了两件主要的事情:挂载DOM和数据监控。
挂载DOM:
- 首先,将组件实例的
$el属性设置为要挂载的目标DOM元素。 - 然后,检查是否存在渲染函数,如果不存在,则将
vm.$options.render设置为一个空的虚拟节点(VNode)。 - 接下来,调用
beforeMount生命周期钩子。 - 定义
updateComponent函数,该函数负责执行组件的渲染逻辑。 - 最后,调用
vm._update(vm._render(), hydrating)来执行渲染函数并更新DOM。
- 首先,将组件实例的
数据监控:
- 创建一个
Watcher实例,将updateComponent函数作为其第二个参数。 Watcher实例会立即执行updateComponent函数,完成初始的渲染。- 在
updateComponent函数中使用的所有数据将被Watcher监听,一旦数据发生变化,会触发回调函数进行视图更新。 - 如果是初次渲染,通过
$vnode判断,将_isMounted属性设置为true,并调用mounted生命周期钩子。 - 在更新完成后,
Watcher实例会调用updated生命周期钩子。
- 创建一个
整个挂载阶段的流程包括设置目标元素、检查渲染函数、执行 beforeMount 生命周期钩子、执行渲染函数和更新 DOM,以及创建 Watcher 实例进行数据监控和相应的生命周期钩子调用。