Vue实例从初始化到挂载的完整流程

 更新时间:2025年12月25日 09:50:15   作者:全栈陈序员  
本文详细解析了Vue实例从初始化到挂载的完整流程,首先通过new Vue()触发_init方法进行初始化,依次完成配置合并、生命周期/事件/渲染初始化,需要的朋友可以参考下

一、整体流程概览

当我们执行 new Vue({ ... }) 时,Vue 会经历 初始化 → 编译模板 → 挂载 DOM 三个阶段。整个过程由 _init 方法驱动,最终通过 $mount 完成视图渲染。

核心路径:
new Vue()_init()initState()$mount()mountComponent()_render()_update() → 真实 DOM

二、详细步骤解析

1. 构造函数与_init初始化

源码位置:src/core/instance/index.js

function Vue(options) {
  if (!(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
  • 调用 _init 是整个实例化的起点。
  • 在此之前,Vue 原型上已通过 mixin 注入了各类方法:
    • initMixin → 定义 _init
    • stateMixin$set, $delete, $watch
    • eventsMixin$on, $emit
    • lifecycleMixin_update, $destroy
    • renderMixin_render

2._init中的关键操作

源码位置:src/core/instance/init.js

Vue.prototype._init = function (options) {
  const vm = this;
  vm._uid = uid++;
  vm._isVue = true;

  // 合并配置(处理 mixins / extends)
  if (options && options._isComponent) {
    initInternalComponent(vm, options);
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    );
  }

  // 初始化代理(开发环境)
  if (process.env.NODE_ENV !== 'production') {
    initProxy(vm);
  } else {
    vm._renderProxy = vm;
  }

  vm._self = vm;

  // 初始化生命周期、事件、渲染
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);

  callHook(vm, 'beforeCreate');

  // 初始化依赖注入(inject / provide)
  initInjections(vm);   // 在 data/props 之前
  initState(vm);        // 初始化 props, methods, data, computed, watch
  initProvide(vm);      // 在 data/props 之后

  callHook(vm, 'created');

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

关键结论

  • beforeCreate 时:data / props 尚未初始化,无法访问;
  • created 时:数据已响应式化,但 DOM 还未生成,不能操作 $el
  • 挂载由 $mount 触发。

3. 数据初始化:initState与initData

源码位置:src/core/instance/state.js

export function initState(vm) {
  vm._watchers = [];
  const opts = vm.$options;
  if (opts.props) initProps(vm, opts.props);
  if (opts.methods) initMethods(vm, opts.methods);
  if (opts.data) initData(vm);
  if (opts.computed) initComputed(vm, opts.computed);
  if (opts.watch) initWatch(vm, opts.watch);
}

function initData(vm) {
  let data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};

  // 校验 data 为纯对象
  if (!isPlainObject(data)) { /* warn */ }

  const keys = Object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;

  // 属性名冲突检查(data vs props/methods)
  for (let i = keys.length - 1; i >= 0; i--) {
    const key = keys[i];
    if (props && hasOwn(props, key)) { /* warn */ }
    else if (!isReserved(key)) {
      proxy(vm, '_data', key); // 通过 this.key 访问 vm._data[key]
    }
  }

  // 响应式化
  observe(data, true /* asRootData */);
}

重点

  • 组件中 data 必须是函数(避免多实例共享对象);
  • 通过 proxy 实现 this.messagethis._data.message
  • 最终调用 observe 将 data 转为响应式(基于 Object.defineProperty)。

4. 挂载阶段:$mount与模板编译

源码位置:src/platforms/web/entry-runtime-with-compiler.js

Vue.prototype.$mount = function (el, hydrating) {
  el = el && query(el);
  if (el === document.body || el === document.documentElement) {
    warn('Do not mount Vue to <html> or <body>');
    return this;
  }

  const options = this.$options;
  if (!options.render) {
    let template = options.template;
    if (template) {
      // 处理 string / element 类型的 template
    } else if (el) {
      template = getOuterHTML(el); // 从 el 提取 HTML
    }

    if (template) {
      // 编译 template → render 函数
      const { render, staticRenderFns } = compileToFunctions(template, {}, this);
      options.render = render;
      options.staticRenderFns = staticRenderFns;
    }
  }

  // 调用真正的 mount
  return mount.call(this, el, hydrating);
}

关键点

  • 若无 render 函数,则尝试从 templateel 提取模板;
  • 通过 compileToFunctions 将模板编译为 render 函数;
  • 编译三步:HTML → AST → render 字符串 → render 函数

5. 渲染组件:mountComponent

源码位置:src/core/instance/lifecycle.js

export function mountComponent(vm, el, hydrating) {
  vm.$el = el;

  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    // 警告:运行时版本缺少编译器
  }

  callHook(vm, 'beforeMount');

  // 定义更新函数
  let updateComponent = () => {
    vm._update(vm._render(), hydrating);
  };

  // 创建渲染 Watcher(核心!)
  new Watcher(vm, updateComponent, noop, {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);

  hydrating = false;

  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }

  return vm;
}

核心机制

  • 创建一个 渲染 Watcher,监听响应式数据变化;
  • 初次执行 updateComponent → 触发首次渲染;
  • 数据变更时,自动触发 beforeUpdate → 重新 _render_update

6. 生成 VNode 与更新 DOM

_render:生成虚拟 DOM

Vue.prototype._render = function () {
  const { render } = this.$options;
  let vnode;
  try {
    vnode = render.call(this._renderProxy, this.$createElement);
  } catch (e) { /* error handling */ }
  // 校验 vnode 合法性
  return vnode;
}

_update:将 VNode 转为真实 DOM

Vue.prototype._update = function (vnode, hydrating) {
  const vm = this;
  const prevVnode = vm._vnode;
  vm._vnode = vnode;

  if (!prevVnode) {
    // 初次挂载
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
  } else {
    // 更新
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
}
  • __patch__ 是平台相关方法(Web 端为 patch 函数),负责 VNode diff + DOM 操作

三、总结:挂载全过程

阶段关键动作生命周期钩子
初始化合并配置、初始化 props/data/methods/watchbeforeCreatecreated
编译template → AST → render 函数
挂载创建渲染 Watcher,首次执行 _render + _updatebeforeMountmounted
更新数据变化 → 触发 Watcher → 重新渲染beforeUpdateupdated

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

以上就是Vue实例从初始化到挂载的完整流程的详细内容,更多关于Vue实例挂载流程的资料请关注脚本之家其它相关文章!

相关文章

  • Vuejs第六篇之Vuejs与form元素实例解析

    Vuejs第六篇之Vuejs与form元素实例解析

    本文通过实例给大家详细介绍了Vuejs与form元素的相关知识,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-09-09
  • Vue使用MD5对前后端进行加密的实现

    Vue使用MD5对前后端进行加密的实现

    前后端分离的项目,遇到了对密码进行加密的情况,在前端或者是在后端加密都是可以的,本文主要介绍了Vue使用MD5对前后端进行加密的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • 使用Element-UI的NavMenu如何隐藏自带的小箭头

    使用Element-UI的NavMenu如何隐藏自带的小箭头

    这篇文章主要介绍了使用Element-UI的NavMenu如何隐藏自带的小箭头问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • 浅谈vue的踩坑路

    浅谈vue的踩坑路

    下面小编就为大家带来一篇浅谈vue的踩坑路。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • vue父组件异步如何获取数据传给子组件

    vue父组件异步如何获取数据传给子组件

    这篇文章主要介绍了vue父组件异步如何获取数据传给子组件问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • vue3.0中sass全局的使用过程

    vue3.0中sass全局的使用过程

    这篇文章主要介绍了vue3.0中sass全局的使用过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • vue中设置echarts宽度自适应的代码步骤

    vue中设置echarts宽度自适应的代码步骤

    这篇文章主要介绍了vue中设置echarts宽度自适应的问题及解决方案,常常需要做到echarts图表的自适应,一般是根据页面的宽度做对应的适应,本文记录一下设置echarts图表的自适应的步骤,需要的朋友可以参考下
    2022-09-09
  • 解决Vue使用百度地图BMapGL内存泄漏问题 Out of Memory

    解决Vue使用百度地图BMapGL内存泄漏问题 Out of Memory

    这篇文章主要介绍了解决Vue使用百度地图BMapGL内存泄漏问题 Out of Memory,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • 解决vue创建项目使用vue-router和vuex报错Object(...)is not a function

    解决vue创建项目使用vue-router和vuex报错Object(...)is not a&nb

    这篇文章主要介绍了解决vue创建项目使用vue-router和vuex报错Object(...)is not a function问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Vue+elementUI el-input输入框手机号校验功能

    Vue+elementUI el-input输入框手机号校验功能

    这篇文章主要介绍了Vue+elementUI el-input输入框手机号校验功能,限制input框内只能输入数字,且为11位,通过实例代码介绍了对输入手机号做校验的方法,感兴趣的朋友跟随小编一起看看吧
    2023-10-10

最新评论