

一、整体流程概览
当我们执行 new Vue({ ... }) 时,Vue 会经历 初始化 → 编译模板 → 挂载 DOM 三个阶段。整个过程由 _init 方法驱动,最终通过 $mount 完成视图渲染。
核心路径:
new Vue()→_init()→initState()→$mount()→mountComponent()→_render()→_update()→ 真实 DOM
二、详细步骤解析
1. 构造函数与 _init 初始化
源码位置:src/core/instance/index.js
1function Vue(options) { 2 if (!(this instanceof Vue)) { 3 warn('Vue is a constructor and should be called with the `new` keyword') 4 } 5 this._init(options) 6} 7
- 调用
_init是整个实例化的起点。 - 在此之前,Vue 原型上已通过 mixin 注入了各类方法:
initMixin→ 定义_initstateMixin→$set,$delete,$watcheventsMixin→$on,$emitlifecycleMixin→_update,$destroyrenderMixin→_render
2. _init 中的关键操作
源码位置:src/core/instance/init.js
1Vue.prototype._init = function (options) { 2 const vm = this; 3 vm._uid = uid++; 4 vm._isVue = true; 5 6 // 合并配置(处理 mixins / extends) 7 if (options && options._isComponent) { 8 initInternalComponent(vm, options); 9 } else { 10 vm.$options = mergeOptions( 11 resolveConstructorOptions(vm.constructor), 12 options || {}, 13 vm 14 ); 15 } 16 17 // 初始化代理(开发环境) 18 if (process.env.NODE_ENV !== 'production') { 19 initProxy(vm); 20 } else { 21 vm._renderProxy = vm; 22 } 23 24 vm._self = vm; 25 26 // 初始化生命周期、事件、渲染 27 initLifecycle(vm); 28 initEvents(vm); 29 initRender(vm); 30 31 callHook(vm, 'beforeCreate'); 32 33 // 初始化依赖注入(inject / provide) 34 initInjections(vm); // 在 data/props 之前 35 initState(vm); // 初始化 props, methods, data, computed, watch 36 initProvide(vm); // 在 data/props 之后 37 38 callHook(vm, 'created'); 39 40 // 如果有 el,自动挂载 41 if (vm.$options.el) { 42 vm.$mount(vm.$options.el); 43 } 44} 45
✅ 关键结论:
beforeCreate时:data / props 尚未初始化,无法访问;created时:数据已响应式化,但 DOM 还未生成,不能操作$el;- 挂载由
$mount触发。
3. 数据初始化:initState 与 initData
源码位置:src/core/instance/state.js
1export function initState(vm) { 2 vm._watchers = []; 3 const opts = vm.$options; 4 if (opts.props) initProps(vm, opts.props); 5 if (opts.methods) initMethods(vm, opts.methods); 6 if (opts.data) initData(vm); 7 if (opts.computed) initComputed(vm, opts.computed); 8 if (opts.watch) initWatch(vm, opts.watch); 9} 10 11function initData(vm) { 12 let data = vm.$options.data; 13 data = vm._data = typeof data === 'function' 14 ? getData(data, vm) 15 : data || {}; 16 17 // 校验 data 为纯对象 18 if (!isPlainObject(data)) { /* warn */ } 19 20 const keys = Object.keys(data); 21 const props = vm.$options.props; 22 const methods = vm.$options.methods; 23 24 // 属性名冲突检查(data vs props/methods) 25 for (let i = keys.length - 1; i >= 0; i--) { 26 const key = keys[i]; 27 if (props && hasOwn(props, key)) { /* warn */ } 28 else if (!isReserved(key)) { 29 proxy(vm, '_data', key); // 通过 this.key 访问 vm._data[key] 30 } 31 } 32 33 // 响应式化 34 observe(data, true /* asRootData */); 35} 36
✅ 重点:
- 组件中
data必须是函数(避免多实例共享对象); - 通过
proxy实现this.message→this._data.message; - 最终调用
observe将 data 转为响应式(基于Object.defineProperty)。
4. 挂载阶段:$mount 与模板编译
源码位置:src/platforms/web/entry-runtime-with-compiler.js
1Vue.prototype.$mount = function (el, hydrating) { 2 el = el && query(el); 3 if (el === document.body || el === document.documentElement) { 4 warn('Do not mount Vue to <html> or <body>'); 5 return this; 6 } 7 8 const options = this.$options; 9 if (!options.render) { 10 let template = options.template; 11 if (template) { 12 // 处理 string / element 类型的 template 13 } else if (el) { 14 template = getOuterHTML(el); // 从 el 提取 HTML 15 } 16 17 if (template) { 18 // 编译 template → render 函数 19 const { render, staticRenderFns } = compileToFunctions(template, {}, this); 20 options.render = render; 21 options.staticRenderFns = staticRenderFns; 22 } 23 } 24 25 // 调用真正的 mount 26 return mount.call(this, el, hydrating); 27} 28
✅ 关键点:
- 若无
render函数,则尝试从template或el提取模板; - 通过
compileToFunctions将模板编译为render函数; - 编译三步:HTML → AST → render 字符串 → render 函数。
5. 渲染组件:mountComponent
源码位置:src/core/instance/lifecycle.js
1export function mountComponent(vm, el, hydrating) { 2 vm.$el = el; 3 4 if (!vm.$options.render) { 5 vm.$options.render = createEmptyVNode; 6 // 警告:运行时版本缺少编译器 7 } 8 9 callHook(vm, 'beforeMount'); 10 11 // 定义更新函数 12 let updateComponent = () => { 13 vm._update(vm._render(), hydrating); 14 }; 15 16 // 创建渲染 Watcher(核心!) 17 new Watcher(vm, updateComponent, noop, { 18 before() { 19 if (vm._isMounted && !vm._isDestroyed) { 20 callHook(vm, 'beforeUpdate'); 21 } 22 } 23 }, true /* isRenderWatcher */); 24 25 hydrating = false; 26 27 if (vm.$vnode == null) { 28 vm._isMounted = true; 29 callHook(vm, 'mounted'); 30 } 31 32 return vm; 33} 34
✅ 核心机制:
- 创建一个 渲染 Watcher,监听响应式数据变化;
- 初次执行
updateComponent→ 触发首次渲染; - 数据变更时,自动触发
beforeUpdate→ 重新_render→_update。
6. 生成 VNode 与更新 DOM
_render:生成虚拟 DOM
1Vue.prototype._render = function () { 2 const { render } = this.$options; 3 let vnode; 4 try { 5 vnode = render.call(this._renderProxy, this.$createElement); 6 } catch (e) { /* error handling */ } 7 // 校验 vnode 合法性 8 return vnode; 9} 10
_update:将 VNode 转为真实 DOM
1Vue.prototype._update = function (vnode, hydrating) { 2 const vm = this; 3 const prevVnode = vm._vnode; 4 vm._vnode = vnode; 5 6 if (!prevVnode) { 7 // 初次挂载 8 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); 9 } else { 10 // 更新 11 vm.$el = vm.__patch__(prevVnode, vnode); 12 } 13} 14
__patch__是平台相关方法(Web 端为patch函数),负责 VNode diff + DOM 操作。
三、总结:挂载全过程
| 阶段 | 关键动作 | 生命周期钩子 |
|---|---|---|
| 初始化 | 合并配置、初始化 props/data/methods/watch | beforeCreate → created |
| 编译 | template → AST → render 函数 | — |
| 挂载 | 创建渲染 Watcher,首次执行 _render + _update | beforeMount → mounted |
| 更新 | 数据变化 → 触发 Watcher → 重新渲染 | beforeUpdate → updated |
💡 一句话概括:
Vue 实例挂载的本质是——将响应式数据通过 render 函数生成 VNode,再通过 patch 算法高效更新到真实 DOM 上,整个过程由一个 渲染 Watcher 驱动。
参考文献


《Vue 实例挂载的过程是怎样的?》 是转载文章,点击查看原文。