
## 前言
最近在深入学习 **Vue.js设计与实现** ,深感“理解设计理念”比“记忆API用法”更为重要。
我决定通过文章来记录和输出学习内容,这不仅是为了检验自己的理解程度,更是为了梳理Vue.js背后的核心思想和实现机制。希望这种“知其所以然”的探索,能为你带来启发。
### 1.1命令式和声明式
首先什么是声明式和命令式,看接下来我们要做的事
获取 id 为 app 的 div 标签
它的文本内容为 hello world
为其绑定点击事件
当点击时弹出提示:ok**命令式代码**:你如何去做,自己手动操作DOM,核心概念就是 **关注过程**
const div = document.querySelector('#app') // 获取 div
div.innerText = 'hello world' // 设置文本内容
div.addEventListener('click', () => { alert('ok') }) // 绑定点击事件**声明式代码**:你描述想要的结果,Vue自动帮你实现DOM操作,核心概念就是 **关注结果**
<div @click="() => alert('ok')">hello world</div>更通俗一点的说法:
* **命令式代码**是厨师,你需要洗菜、切菜、炒菜,每个步骤都需要你来
* **声明式代码**是顾客,我只需要跟你说一声我要吃什么,接下来就交给你了
### 1.2 性能和可维护性的权衡
声明式代码好是好,但他会有**更多的性能消耗**,借助书中的一句结论:**声明式代码的性能不优于命令式代码的性能**
看上面的例子 要求:把div中的文本要求改成 **hzh 真帅**。
**命令式代码**:
div.textContent = 'hzh 真帅'**声明式代码**:
<div @click="handleClick">{{ message }}</div>
const app = {
data() {
return {
message: 'hello world'
}
},
methods: {
handleClick() {
// 声明式更新:只改变数据,Vue自动更新DOM
this.message = 'hzh 真帅';
}
}
}
//当我们的this.message = 'hzh 真帅'的时候,vue自动做了这么一步
div.textContent = 'hzh 真帅'书中结论:
如果我们把**直接修改**的性能消耗定义为 **A**,把**找出差异**的性能消耗定义为 **B**,那么有: <br/>
● 命令式代码的更新性能消耗 = A <br/>
● 声明式代码的更新性能消耗 = B + A
那既然声明式代码会消耗更多的性能,那我们是不是要更多的使用命令式代码?答案是不一定,直接上截图

其实对于大多数业务应用,声明式的可维护性优势远大于其性能开销。而且随着框架优化技术的进步,这个性能差距正在不断缩小。这也是为什么vue,react这些声明式框架会成为主流
### 1.3 虚拟DOM的性能到底如何
首先,虚拟DOM是什么?先看看真实DOM是什么
**真实DOM**
// 真实DOM - 浏览器中的实际节点
<div class="title" id="app">Hello World</div>**虚拟DOM**
// 对应的虚拟DOM
const vnode = {
type: 'div',
props: {
class: 'title',
id: 'app'
},
children: 'Hello World'
}可以看出,虚拟DOM就是一个js对象,为什么要有虚拟DOM这个东西呢,回想之前的**声明式代码**,把**找出差异**的性能消耗定义为 **B**,虚拟DOM就是为了优化这个'找出差异(B)'的性能消耗(书中原话:虚拟DOM就是为了最小化找到差异这一步性能消耗而出现的)
DOM的更新方式以下有几种
1. 原生的js操作
2. innerHTML
3. 虚拟DOM
**innerHTML**更新需要触发需要**删除以前的元素**,在**重新更新新的元素**
**innerHTML**创建页面的性能:**HTML 字符串拼接的计算量** + **innerHTML 的 DOM 计算量**
<div id="list">
<li>项目1</li>
<li>项目2</li>
</div>
// 更新列表 - 重新渲染整个列表
const list = document.getElementById('list');
list.innerHTML = `
<li>项目1</li>
<li>项目2</li>
<li>新项目3</li> <!-- 只增加了这一项 -->
`;
**虚拟DOM**创建js对象,这个对象可以**理解**成真实DOM,并使用**递归地遍历**虚拟 DOM 树并创建真实 DOM
**虚拟DOM**创建页面的性能:**创建 JavaScript 对象的计算量** + **创建真实 DOM 的计算量**
// 旧的虚拟DOM
const oldVNodes = [
{ type: 'li', children: '项目1' },
{ type: 'li', children: '项目2' }
];
// 新的虚拟DOM
const newVNodes = [
{ type: 'li', children: '项目1' },
{ type: 'li', children: '项目2' },
{ type: 'li', children: '新项目3' }
];
那么他两的区别在哪呢?
**innerHTML**的更新过程:innerHTML的更新过程会销毁所有现有DOM元素,然后重新创建新的DOM元素
大概意思是:
原本:list.innerHTML = `
<li>项目1</li>
<li>项目2</li>
`;
->
销毁之后:list.innerHTML = ``
->
重新更新:list.innerHTML = `
<li>项目1</li>
<li>项目2</li>
<li>新项目3</li>
`;
**虚拟DOM**的更新过程:javaScript对象+diff,只会更新更改的元素
// 虚拟DOM如何工作
const oldVNode = {
type: 'ul',
children: [
{ type: 'li', children: 'Item 1' },
{ type: 'li', children: 'Item 2' }
]
};
const newVNode = {
type: 'ul',
children: [
{ type: 'li', children: 'Item 1' },
{ type: 'li', children: 'Updated Item 2' }, // 只有这里变了
{ type: 'li', children: 'Item 3' } // 新增
]
};
// Diff算法发现:
// - 第一个li没变,复用
// - 第二个li文本变了,只更新文本
// - 新增第三个li,创建新元素
虚拟DOM的优势在于:无论页面有多大,我**只更新变化**的地方,而innerHTML则是**全部销毁更新**,这对性能消耗很大

书中原话:innerHTML、虚拟 DOM 以及原生 JavaScript 在更新页面时的性能我们分了几个维度:**心智负担**、**可维护性**和**性能**

### 1.4编译时和运行时
当设计一个框架的时候,我们有三种选择:**纯运行时**的、**运行时** +**编译时**的或**纯编译时**的
**运行时**
假设一个框架,提供一个render函数,我们可以提供一个树型结构的对象
const obj = {
tag: 'div',
children: [
{ tag: 'span', children: 'hello world' }
]
}tag代表标签名称,children即可以是一个数组(代表子元素),也可以是一段文本内容,接下来实现一下render函数
function Render(obj, root) {
const el = document.createElement(obj.tag)
if (typeof obj.children === 'string') {
const text = document.createTextNode(obj.children)
el.appendChild(text)
} else if (obj.children) {
// 数组,递归调用 Render,使用 el 作为 root 参数
obj.children.forEach((child) => Render(child, el))
}
// 将元素添加到 root
root.appendChild(el)
}有了这个函数之后,我们可以这样使用来渲染到body下
Render(obj, document.body)**编译时**
有时我们觉得,直接写一个树型结构的对象好麻烦啊,我能不能**获取一下html标签把他变成树型结构的对象**呢?,为此写了一个Compiler函数,可以实现上述效果
const html = `
<div>
<span>hello world</span>
</div>
`
// 调用 Compiler 编译得到树型结构的数据对象
const obj = Compiler(html)Compiler函数做的事:将html编译成命令式代码的过程

那么这三种选择分别有哪些优缺点呢?
**运行时**:
* ✅ **无构建步骤**:直接引入即可使用
* ✅ **灵活性高**:可以在运行时动态创建任何结构
* ✅ **调试简单**:没有编译过程,代码就是最终代码
* ❌ **性能较差**:无法进行编译时优化
* ❌ **代码冗长**:需要手动创建DOM和绑定事件
* ❌ **无语法糖**:没有模板、JSX等便捷语法
**编译时**:
* ✅ **性能极佳**:编译时完成所有优化,运行时几乎零开销
* ✅ **包体积小**:不需要包含运行时框架代码
* ✅ **无虚拟DOM**:直接操作DOM,减少内存占用
* ❌ **灵活性差**:难以在运行时动态创建复杂结构
* ❌ **调试困难**:编译后代码与源代码差异大
* ❌ **构建依赖**:必须使用构建工具,无法直接运行
**编译时 + 运行时**:
* ✅ **开发体验好**:提供模板、JSX等友好语法
* ✅ **性能优化**:编译时可以进行静态分析优化
* ✅ **灵活性**:支持运行时动态创建
* ✅ **渐进式**:可以选择使用编译特性
* ⚠️ **需要构建工具**:需要配置webpack、vite等
* ⚠️ **包体积较大**:包含编译器和运行时
* ⚠️ **复杂度高**:需要处理编译和运行时的协调
## 总结
通过对比命令式与声明式、分析虚拟DOM原理、了解编译时与运行时的选择,我们看到了Vue.js在性能与可维护性之间的智慧平衡。这种"知其所以然"的理解,能让我们更好地使用和欣赏这个优秀的框架。
希望这篇文章对你有所启发!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。