在 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
javascriptrender: function (createElement) { return createElement(App); }Vue 在调用
render函数时,会自动传进来一个工具函数,专门用来创建 DOM 元素,这个工具函数叫createElement。 -
第一步化简(换个简短的名字):
前端圈子里习惯把
createElement简写成字母h(源自 Hyperscript,意为"用 JS 编写 HTML 的脚本")。
JavaScriptjavascriptrender: 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 = ...(保存变量)
vm 是 ViewModel 的缩写(源于 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 结构替换上去。 -
绑定数据仓库 :把
store或data里的响应式数据和页面连在一起。 -
自动监工(动态更新) :当你点击按钮修改了数据(如
sum增加了),vm会立刻重新执行render,算出全新的页面,并自动更新#app里的内容。 -
或者换个理解方式,很多初学者会产生疑问:既然我们写好了
App.vue组件,为什么不能像引入标准 HTML 标签那样,直接把它塞进 DOM 节点中?1.
.vue文件本质是一个 JS 配置对象App.vue经过 Vue 单文件组件编译器(SFC Compiler)编译后,在内存中生成的不过是一个标准的 JavaScript 配置对象:
JavaScriptjavascript// App.vue 编译后的本质概览: export default { name: 'App', data() { return { sum: 0 } }, methods: { ... }, render() { /* ... */ } // 由 <template> 编译而来的渲染函数 }浏览器 DOM 引擎只认识
<div>、<span>等标准 HTML 元素,根本无法直接读取和运行一个包含了data、methods和自定义逻辑的 JS 对象。2. 必须存在一个"运行引擎/管理者"
为了让这个配置对象呈现在屏幕上,必须引入一个框架级的运行引擎。这个引擎需要依次完成以下脏活累活:
-
数据响应式拦截 :拦截
data或state中的数据变化。 -
构建虚拟 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
javascriptapp.use(pinia) // 注册状态管理 app.use(router) // 注册路由 -
第 3 步:
app.mount('#app')触发施工 当调用.mount('#app')时,Vue 3 内部的渲染引擎才会真正启动:-
读取
App组件的内容生成虚拟 DOM。 -
实例化真正的根组件。
-
把虚拟 DOM 转化为真实 DOM 渲染并替换掉
#app。
-
-
二者本质的区别主要在于以下三个维度:
-
1. 填充者的"体量与身份"不同
-
Vue 2 的
vm填入 :vm本身就是一个极为庞大的对象,内部塞满了响应式监听器(Observer)、依赖收集(Dep)、生命周期钩子以及虚拟 DOM。让它直接去对接 DOM,相当于把"应用指挥部"和"施工队伍"捆绑成了同一个笨重的大块头。 -
Vue 3 的
app.mount()填入 :app本身只是一个非常轻量的 JS 对象(里面仅有use、component、mount等几个方法)。它不包含响应式数据,也不占用组件内存,它只充当启动开关,调用底层解耦的"渲染器(Renderer)"去完成具体的 DOM 填充。 -
2. 填充区域的"环境隔离度"不同
-
vm填入(容易互相污染) : Vue 2 挂载vm时,全局组件和插件是注册在Vue构造函数上的。如果在同一个页面挂载两个vm(比如#app1和#app2),它们会共享一套全局配置,无法做到彼此独立。 -
3. 拔出(卸载)时的处理机制不同
-
Vue 2 卸载
vm(只管销毁,不管清场) : 如果在 Vue 2 里想销毁这个页面,调用vm.$destroy()只能切断组件内部的响应式监听,真实的 HTML 标签依然残留在 DOM 树里 ,必须手动去removeChild。 -
Vue 3 卸载
app(彻底清场) : Vue 3 提供了官方的app.unmount()方法。一键调用后,它会自动销毁内部的所有组件树,并彻底把#app花盆里的真实 DOM 清理干净,不留任何垃圾。 -
app.mount()填入(完全独立的沙盒) : Vue 3 挂载时,app1.mount('#app1')和app2.mount('#app2')是两个完全隔离的"气泡"。给app1挂载的插件(如路由、状态库),绝对不会渗漏到app2的 DOM 区域中。