Vue 实例挂载的过程是怎样的?

作者:全栈陈序员日期:2025/12/25

在这里插入图片描述
在这里插入图片描述

一、整体流程概览

当我们执行 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 → 定义 _init
    • stateMixin$set, $delete, $watch
    • eventsMixin$on, $emit
    • lifecycleMixin_update, $destroy
    • renderMixin_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. 数据初始化:initStateinitData

源码位置: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.messagethis._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 函数,则尝试从 templateel 提取模板;
  • 通过 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/watchbeforeCreate → created
编译template → AST → render 函数
挂载创建渲染 Watcher,首次执行 _render + _updatebeforeMount → mounted
更新数据变化 → 触发 Watcher → 重新渲染beforeUpdate → updated

💡 一句话概括
Vue 实例挂载的本质是——将响应式数据通过 render 函数生成 VNode,再通过 patch 算法高效更新到真实 DOM 上,整个过程由一个 渲染 Watcher 驱动。


参考文献


在这里插入图片描述
在这里插入图片描述


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


相关推荐


从已损坏的备份中拯救数据
神奇的程序员2025/12/17

前言 12月15号早上,一觉醒来,拿起手机看到我的邮箱收到了内网服务无法访问的告警邮件,本以为只是简单的服务卡死,将服务器重启后就去上班了。 后来,陆续有好友联系我说网站挂了。 定位问题 晚上下班回家后,尝试将电脑断电重启,发现pve只能存活2分钟左右,然后整个系统卡死,无法进行任何操作。首先,我想到的是:会不会某个vm虚拟机或者ct容器影响到宿主机了。 因为系统只能存活几分钟,在执行禁用操作的时候,强制重启了好几次服务器。当所有的服务都停止启动后,卡死的问题依旧存在。 翻日志 没辙了,这已经


苹果ios手机ipad安装配置ish终端shell工具
无痕melody2025/12/9

简介 官方介绍 iSH 是一个运行在 iOS 上的 Linux Shell,用来在ARM架构的 iOS 设备上模拟 X86 架构。也就是说不光是 IPad 可以安装,IPhone 上也可以安装运行 iSH,直接在 IOS 设备上运行 Linux 环境,而且免费! 如果你正在使用的电脑是 Mac,那么可以把 iSH 比作你电脑上面的终端。 iSH 官方地址 安装 AppStore里搜索ish或手机打开链接 配置 基本操作 操作按钮 2. 这个按钮相当于电脑上的 Tab 键,用于命令


为什么C语言拒绝函数重载?非要重载怎么做?
码事漫谈2025/11/29

在我们学习C++、Java或C#时,函数重载(Function Overloading)是一个再自然不过的概念:允许两个或多个函数使用相同的名字,只要它们的参数列表(参数的类型、个数或顺序)不同即可。编译器会根据调用时传入的实参,自动选择最匹配的那个函数。 然而,当我们回到C语言的世界,这条规则却失效了。如果你定义了两个同名的函数,即使参数列表不同,编译器也会毫不留情地报出一个“重定义”错误。 那么,为什么C语言的设计者,要“剥夺”这个看似非常实用的特性呢? 答案并非“不能”,而是“不为”。这背


Go 项目结构总是写乱?这个 50 行代码的 Demo 教你标准姿势
Java小成2026/1/4

1. 场景复现:那个让我头疼的时刻 去年,我接手了一个"祖传" Go 项目。打开代码仓库的那一刻,我整个人都不好了——所有代码都塞在一个 main.go 里,足足 3000 多行。想加个功能?先花半小时找代码在哪。想写个单元测试?抱歉,函数全是私有的,而且互相耦合,根本没法单独测。 我当时就在想:如果当初写这个项目的人,能从第一天就用一个规范的结构,后面的人得少掉多少头发? 后来我开始研究 Go 官方和社区推荐的项目布局,发现其实规则很简单,但很多人就是不知道。于是我写了这个 50 行代码的小


Ansible自动化(十五):加解密详解
cly12026/1/12

Ansible Vault 是 Ansible 提供的一套用于保护敏感数据的机制,可以对各类配置文件进行加密,防止敏感信息(如密码、私钥、API 密钥等)以明文形式暴露在代码仓库或配置文件中。 一、为什么需要 Ansible 加密? 场景说明: Playbook 中包含数据库密码、API Token、SSH 私钥等敏感信息Inventory(主机清单)中直接写入了连接密码(如 ansible_password)变量文件(vars/main.yml)中包含机密配置 ✅ Ansible Vaul


筑牢金融底座:企业级区块链全球化数据库架构设计白皮书
China_Yanhy2026/1/20

📖 前言:Web3 业务的双重账本 在 Web3 业务中,区块链(AMB)是不可篡改的“链上真理”,而关系型数据库(RDS/Aurora)则是承载用户资产、撮合逻辑和KYC信息的“链下业务核心”。对于追求全球化的高频交易项目,数据库的架构设计必须解决两个核心矛盾:跨国访问的物理延迟 与 资金数据的一致性。 第一部分:旗舰方案 —— Amazon Aurora Global Database (深度解析) 这是针对跨国交易所(如币安、Coinbase 模式)的首选架构。 1. 核心架构

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客