Appearance
💡 什么是虚拟 DOM?
通过 JavaScript 对象来描述真实 DOM 树的结构。这个对象包含了节点的各种关键信息:
📝 核心组成部分
props
⚡️:保存节点的各种属性信息tag
🏷:标记节点的类型标签children
🌲:记录所有子节点信息
💫 简而言之,虚拟 DOM 就是这样一个 JavaScript 对象,它映射和模拟了真实的 DOM 结构。
🤔 那真实 DOM 是指什么
js
const div = document.createElement('div')
🌐 WebIDL (Web Interface Definition Language
)
📝 WebIDL 翻译成中文就是 web 接口定义语言,定义了:
- js 与浏览器之间的通信 🌉
- DOM 接口的描述
🔧 通过 WebIDL,浏览器开发者 可以:
- 🎯 描述 JavaScript 可调用的类和方法
- 🔄 描述映射到 JavaScript 中的对象和方法
c++
// 1. webIDL 定义,创建 DOM 元素
interface Document {
Element createElement(DOMString tagName);
}
// 2. 浏览器开发者接口实现
class Document {
Element* createElement(const std::string& tagName) {
return new Element(tagName);
}
}
// 3. 生成绑定代码
void Document_createElement(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
Document* document = args.Holder()->GetInternalField(0);
// 由 webIDL 编译器生成,从js到c++的绑定代码
// 获取 tagName
std::string tagName = args[0]->ToString();
// 创建 Element 对象
Element* element = document->createElement(tagName);
// 将 Element 对象返回给 js
args.GetReturnValue().Set(element);
}
// 4. 在 js 引擎中注册
// AI 生成
void RegisterDocument(v8::Isolate* isolate) {
v8::Local<v8::FunctionTemplate> documentTemplate = v8::FunctionTemplate::New(isolate, Document_createElement);
v8::Local<v8::ObjectTemplate> documentObjectTemplate = documentTemplate->InstanceTemplate();
documentObjectTemplate->SetInternalFieldCount(1);
v8::Local<v8::Object> documentObject = documentObjectTemplate->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
}
js
const div = document.createElement('div')
真实 DOM 的创建
🔹 所以真实 DOM 的产生就是一个 C++ 方法的调用,而不是普通的 js 函数
重新渲染绘制 呢 TODO
⚙️ 执行流程
JavaScript 引擎处理
- 将代码识别为特殊的 API 调用
- 向浏览器内核发送创建元素的请求
浏览器处理
- 接收请求并通过 C++ 实现创建 DOM 元素
- 创建完成后返回对应的 JavaScript 对象
txtA[JavaScript 代码] -> B[C++ 方法调用] B -> C[创建 DOM 元素] C -> D[返回 JS 对象]
Vue 中的虚拟 DOM
vue
<script setup>
import {h} from 'vue'
import Child from './child.vue'
const vNode = h(Child)
console.log('🚀 ~ vNode:', vNode)
</script>
🤔 为什么需要虚拟 DOM
在 Web 开发的早期阶段,开发者主要通过直接操作 DOM 的方式来更新页面内容。这种方式被称为 命令式编程 ,从性能角度来看确实是最优的选择。
js
// 增
const div = document.createElement('div')
document.body.appendChild(div)
// 删
document.body.removeChild(div)
// 改
div.innerHTML = 'hello'
// 查
const div = document.querySelector('div')
两种方式的对比
js
// 1. 命令式编程
const app = document.getElementById('app')
const messageDiv = document.createElement('div')
messageDiv.className = 'message'
const infoDiv = document.createElement('div')
infoDiv.className = 'info'
app.appendChild(messageDiv)
app.appendChild(infoDiv)
// 2. 声明式编程
app.innerHTML = '
< div
class
= "message" > hello < /div>
<div class="info">world</div>
'
🚀 命令式编程
- ✅ 性能最优
- ❌ 开发复杂度高
- ❌ 容易产生 bug
- ❌ 维护成本高
🌟 声明式编程
- ✅ 开发效率高
- ✅ 代码可维护性强
- ✅ 降低开发者心智负担
- ⚠️ 性能略有损耗
💡 虽然命令式编程在性能上占优,但现代开发者更倾向于采用声明式编程。因为开发效率和可维护性的提升,往往能够弥补些许的性能损耗。
🤔 性能损耗在什么地方
计算过程
🎯 声明式编程 需要经过两个计算层面:
- JavaScript 层面: 解析模板字符串
- DOM 层面: 构建实际的 DOM 节点结构
💫 虚拟 DOM 同样涉及双层计算:
- 📊 JavaScript 层面: 构建虚拟 DOM 对象
- 🎨 DOM 层面: 基于虚拟 DOM 创建真实 DOM
🔍 性能对比测试:
js
console.time('time')
const arr = []
for (let i = 0; i < 10000000; i++) {
const div = {a: 1}
arr.push(div)
}
console.timeEnd('time')
// ----------------------------
console.time('time')
const arr = []
for (let i = 0; i < 10000000; i++) {
const div = document.createElement('div')
arr.push(div)
}
console.timeEnd('time')
操作类型 | 耗时 | 说明 |
---|---|---|
JavaScript 对象创建 | ⚡️ 170-200ms | 创建一千万个普通 JS 对象 |
DOM 节点创建 | 🐢 2000+ms | 创建一千万个 DOM 节点 |
document.createElement('div')
会被识别为 API 调用,等待渲染器引擎反馈结果,所以性能差距很大
🎯 虚拟 DOM 的性能优势
html
<body>
<div class="container"></div>
<button id="update">update</button>
<script>
const container = document.querySelector('.container')
update.addEventListener('click', () => {
container.innerHTML = new Date().toLocaleString()
})
</script>
</body>
🔄 直接操作 DOM 的计算层面
在这个简单的示例中,直接操作 DOM 需要经过以下几个计算步骤:
- DOM 层面: 销毁旧的 DOM 节点
- JS 层面: 解析新的字符串内容
- DOM 层面: 创建新的 DOM 节点
⚡️ 虚拟 DOM 的计算层面
相比之下,使用虚拟 DOM 只有两个层面的计算:
- JS 层面: 通过 diff 算法计算需要更新的 DOM 节点
- DOM 层面: 使用 patch 算法更新必要的 DOM 节点
所以虚拟 DOM 的真正优势在于更新阶段的性能表现:
- 初始渲染:与直接操作 DOM 相比差距不大
- 更新阶段:通过 Diff 算法智能计算最小更新范围,显著提升性能
- 批量更新:可以将多次更新合并处理,减少 DOM 操作次数
🤔 其他优势
跨平台
UI 结构已经描述好了,可以通过不同的渲染引擎进行渲染,而不只是浏览器平台。比如可以渲染到:
- 🖥 桌面应用 (Electron)
- 📱 移动应用 (React Native/Weex)
- 🎮 小程序平台
- 🖨 服务器端渲染(SSR)