Vue 2 与 Vue 3 在应用初始化上的设计差异与技术演进

在 Vue 框架的发展历程中,入口文件(main.js)的初始化代码虽然只有短短几行,却代表了框架底层架构思想的根本变革。本文将深度解析 Vue 2 与 Vue 3 在应用初始化上的设计差异与技术演进。

语法演进与代码对比

从写法上看,Vue 3 用工厂函数取代了 Vue 2 的构造函数实例化:

javascript 复制代码
// ================= Vue 2 写法 =================
import Vue from 'vue'
import App from './App.vue'

const vm = new Vue({
  render: h => h(App)
}).$mount('#app')

// ================= Vue 3 写法 =================
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

先从vue2开始讲起。

在 Vue 2 中,new Vue() 是整个应用的启动引擎,它创建了一个被称为 vm(ViewModel) 的根组件实例。我们先看看render函数。

  • 完全不简写的原始代码

    JavaScript

    javascript 复制代码
    render: function (createElement) {
      return createElement(App);
    }

    Vue 在调用 render 函数时,会自动传进来一个工具函数,专门用来创建 DOM 元素,这个工具函数叫 createElement

  • 第一步化简(换个简短的名字)

    前端圈子里习惯把 createElement 简写成字母 h (源自 Hyperscript,意为"用 JS 编写 HTML 的脚本")。
    JavaScript

    javascript 复制代码
    render: function (h) {
      return h(App);
    }
  • 第二步化简(改为 ES6 箭头函数)

    去掉 function 关键字并省略单参数的括号,就变成了最终你看到的:render: h => h(App)

(注:脚手架创建的项目为了减小打包体积,默认使用的是"仅运行时"版本的 Vue,这个版本没有内置模板编译器,所以必须用 render 函数配合 h() 来渲染 App 组件。)

2. new Vue({ ... })(启动引擎)

通过 new Vue() 实例化一个 Vue 的根对象,并把配置好的 render 函数传进去,正式启动 Vue 的数据绑定和响应式系统。

3. .$mount('#app')(挂载上墙)

把 Vue 引擎根据 App 组件渲染出来的虚拟 DOM,真正转换成 HTML 标签,插入并替换掉 index.html 文件里那个 <div id="app"></div> 占位节点。

4. const vm = ...(保存变量)

vmViewModel 的缩写(源于 MVVM 模式)。这一步只是把创建好的 Vue 根实例用一个变量存起来,方便在控制台打印调试(如 console.log(vm))。

为了搞懂vue3的新变化 我们必须先搞懂new Vue到底是什么。

页面不是凭空产生的,Vue 引擎需要一份"施工图纸"才能把页面画出来。而这个选项对象,就是你提交给 Vue 引擎的完整施工配置。

1. 为什么要用对象来决定渲染?(核心理念:数据驱动视图) 在传统开发中,你必须手动写 document.getElementById('xx').innerHTML = 'xxx' 一步步去修改 DOM 节点来渲染页面。 但在 Vue 的世界里,你不需要碰真实的 DOM。你只需要在这个"选项对象"里写明规则,Vue 引擎就会根据这个对象自动帮你去操作 DOM。这个对象就是你与 Vue 引擎沟通的唯一语言

2. 选项对象里装了哪些"渲染指令"? 当你把对象传给 new Vue({...}) 时,引擎会从中读取渲染所需的核心信息:

  • render: h => h(App)(画什么):直接提供 UI 骨架。引擎拿到这个属性,就知道最外层的页面长什么样。

  • data: { ... }(填什么):提供页面的动态数据。引擎会把这里的数据填入 UI 骨架中。

  • methods: { ... }(怎么动):提供交互逻辑。引擎会把这些方法绑定到页面对应的点击事件上。

3. Vue 引擎收到这个对象后的执行步骤

正是因为需要这个"引擎"的存在,才有了 Vue 2 的 new Vue() 与 Vue 3 的 createApp()

  • 第一步(读取配置) :引擎解剖你传进来的选项对象,发现里面有一个 render 属性。

  • 第二步(构建虚拟 DOM) :引擎执行 render 函数里的 h(App),把 App 组件转化成一个 JavaScript 树状结构(也就是虚拟 DOM)。在这个阶段,页面还在内存里,屏幕上什么都没有。

  • 第三步(渲染真实 DOM) :最后,通过 $mount('#app') 的指令,引擎将内存里的虚拟 DOM 翻译成浏览器认识的真实 HTML 标签,并把它挂载到屏幕上。

  • 简单来说,vm 确实就是一个集成了画图、监听和渲染功能的"核心引擎(工具)"

    它拿到 render 出来的画面描述(虚拟 DOM)后,主要替你干三件事:

  • 把页面"种"进 DOM :找到 HTML 里的 <div id="app"></div> 占位符,把你组件真正的 HTML 结构替换上去。

  • 绑定数据仓库 :把 storedata 里的响应式数据和页面连在一起。

  • 自动监工(动态更新) :当你点击按钮修改了数据(如 sum 增加了),vm 会立刻重新执行 render,算出全新的页面,并自动更新 #app 里的内容。

  • 或者换个理解方式,很多初学者会产生疑问:既然我们写好了 App.vue 组件,为什么不能像引入标准 HTML 标签那样,直接把它塞进 DOM 节点中?

    1. .vue 文件本质是一个 JS 配置对象

    App.vue 经过 Vue 单文件组件编译器(SFC Compiler)编译后,在内存中生成的不过是一个标准的 JavaScript 配置对象
    JavaScript

    javascript 复制代码
    // App.vue 编译后的本质概览:
    export default {
      name: 'App',
      data() { return { sum: 0 } },
      methods: { ... },
      render() { /* ... */ } // 由 <template> 编译而来的渲染函数
    }

    浏览器 DOM 引擎只认识 <div><span> 等标准 HTML 元素,根本无法直接读取和运行一个包含了 datamethods 和自定义逻辑的 JS 对象。

    2. 必须存在一个"运行引擎/管理者"

    为了让这个配置对象呈现在屏幕上,必须引入一个框架级的运行引擎。这个引擎需要依次完成以下脏活累活:

  • 数据响应式拦截 :拦截 datastate 中的数据变化。

  • 构建虚拟 DOM :执行 render 函数,将配置对象翻译成描述 DOM 结构的 JS 树形结构(VNode)。

  • 真实 DOM 挂载与 Diff 更新 :将 VNode 转化为真实 HTML 标签挂载到 #app 上;当数据变动时,对比新旧 VNode 树并局部更新视图。

而Vue 3 把这一套流程大幅简化了,把"创建管理者"和"动手施工(渲染)"分成了两步自动完成。

在 Vue 3 中,你写的代码只有简短的两行:

JavaScript

java 复制代码
const app = createApp(App) // 1. 创建管理控制台
app.mount('#app')          // 2. 施工并挂载

这两行代码在后台帮干了三件事:

  • 第 1 步:createApp(App) 自动接手"图纸" 在 Vue 2 里,你需要手动写 render: h => h(App) 告诉引擎渲染 App;但在 Vue 3 里,你直接把 App 传给 createApp,Vue 3 自动在底层为你做好了 render 映射 ,不再需要你写那个晦涩的 h => h(App) 箭头函数。

  • 第 2 步:配置独立沙盒环境(通过 app 在调用 mount 之前,你可以在这个轻量的 app 对象上装插件、配置全局变量:

    JavaScript

    javascript 复制代码
    app.use(pinia) // 注册状态管理
    app.use(router) // 注册路由
  • 第 3 步:app.mount('#app') 触发施工 当调用 .mount('#app') 时,Vue 3 内部的渲染引擎才会真正启动:

    1. 读取 App 组件的内容生成虚拟 DOM。

    2. 实例化真正的根组件。

    3. 把虚拟 DOM 转化为真实 DOM 渲染并替换掉 #app

  1. 二者本质的区别主要在于以下三个维度:

  2. 1. 填充者的"体量与身份"不同

  3. Vue 2 的 vm 填入vm 本身就是一个极为庞大的对象,内部塞满了响应式监听器(Observer)、依赖收集(Dep)、生命周期钩子以及虚拟 DOM。让它直接去对接 DOM,相当于把"应用指挥部"和"施工队伍"捆绑成了同一个笨重的大块头。

  4. Vue 3 的 app.mount() 填入app 本身只是一个非常轻量的 JS 对象(里面仅有 usecomponentmount 等几个方法)。它不包含响应式数据,也不占用组件内存,它只充当启动开关,调用底层解耦的"渲染器(Renderer)"去完成具体的 DOM 填充。

  5. 2. 填充区域的"环境隔离度"不同

  6. vm 填入(容易互相污染) : Vue 2 挂载 vm 时,全局组件和插件是注册在 Vue 构造函数上的。如果在同一个页面挂载两个 vm(比如 #app1#app2),它们会共享一套全局配置,无法做到彼此独立。

  7. 3. 拔出(卸载)时的处理机制不同

  8. Vue 2 卸载 vm(只管销毁,不管清场) : 如果在 Vue 2 里想销毁这个页面,调用 vm.$destroy() 只能切断组件内部的响应式监听,真实的 HTML 标签依然残留在 DOM 树里 ,必须手动去 removeChild

  9. Vue 3 卸载 app(彻底清场) : Vue 3 提供了官方的 app.unmount() 方法。一键调用后,它会自动销毁内部的所有组件树,并彻底把 #app 花盆里的真实 DOM 清理干净,不留任何垃圾。

  10. app.mount() 填入(完全独立的沙盒) : Vue 3 挂载时,app1.mount('#app1')app2.mount('#app2') 是两个完全隔离的"气泡"。给 app1 挂载的插件(如路由、状态库),绝对不会渗漏到 app2 的 DOM 区域中。

相关推荐
捌年1 小时前
5分钟搞定工作周报!TRAE Work自动梳理零散工作记录实战
前端
悟空瞎说1 小时前
Three.js 架构实践:用 MVC 模式组织你的 3D 应用
前端·javascript
光影少年2 小时前
react navite本地存储:AsyncStorage、MMKV、文件存储
前端·react native·react.js
曹牧2 小时前
C#:问号
前端·数据库·c#
王林不想说话2 小时前
JavaScript 从入门到进阶:一篇覆盖核心知识、运行机制与工程实践
javascript
windliang2 小时前
Claude Code 源码分析(十一):一段会话怎样保存、恢复与续写
前端·javascript·人工智能
掘金一周2 小时前
掘友们,你们现在下班了都玩啥游戏?| 沸点周刊 8.13
前端·人工智能·后端
HjhIron2 小时前
别再傻等后端接口了!React + Vite + Mock 打造真正的前后端分离开发体验
前端
用户921080262862 小时前
从 CSS 到 Vue Scoped:一篇讲透前端样式的工程化认知
前端