前言
- 常网IT源码上线啦!
- 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
所谓寒冬
是不是只对我一人寒冬?
一、前言
面试官一来就问:我们都知道,vue模板转换为DOM,是经过一系列流程转换的,他底层又是怎么高效更新这些节点的呢?
靓仔,你来说一下。
二、虚拟 DOM
在回答这个问题之前,我认为有必要先了解一下虚拟DOM。
高手可跳过。
虚拟 DOM 是一种抽象概念,它并不直接操作浏览器中的实际 DOM,而是通过数据结构来“虚拟”地表示 UI。
简单来说,虚拟 DOM 就是一个以 JavaScript 对象形式存在的抽象表示,它记录了 UI 中各个元素的结构和属性。这个数据结构与真实的 DOM 树结构类似,但却完全保存在内存中。
虚拟 DOM 的主要作用是帮助开发者更高效地管理界面更新。具体来说,当 UI 发生变化时,框架不会立即修改真实 DOM,而是先创建一棵新的虚拟 DOM 树,再与当前的虚拟 DOM 树进行对比,找出两者之间的差异,最后再将这些变化应用到真实 DOM 上。
我们可以通过以下示例来更直观地理解虚拟 DOM 的结构:
const vnode = {
type: 'div', // 组件或 HTML 元素的类型
props: { // 元素的属性,如 class、id、style 等
id: 'hello 阿泽'
},
children: [ // 子节点
/* 更多的 vnode 对象 */
]
}
三、渲染过程
虚拟 DOM 的核心优势在于其提升了界面更新的效率。具体过程通常分为两个阶段:挂载 和 更新。
1. 挂载(Mounting)
挂载是指初次渲染的过程。当虚拟 DOM 树构建完成后,框架的渲染器会遍历虚拟 DOM 树,将每一个虚拟节点转换为真实 DOM 元素,并将这些元素插入到页面中。
2. 更新(Patching)
更新是指虚拟 DOM 树发生变化时的过程。每当数据或状态发生改变,虚拟 DOM 会生成一棵新的虚拟 DOM 树。渲染器将这棵新的虚拟 DOM 树与当前的虚拟 DOM 树进行对比,找出两者之间的差异。这个过程被称为“比对”(diffing)或“协调”(reconciliation)。
一旦找出差异,渲染器就会只更新那些变化了的部分,而不是重新渲染整个 DOM 树,从而避免了不必要的性能开销。
四、渲染管线
从高层的视角来看,Vue 组件的挂载和更新过程实际上是一个精心优化的工作流,涉及到多个重要步骤。
- 编译阶段:模板转化为渲染函数
- 构建时编译
- 运行时编译
- 挂载阶段:从虚拟 DOM 到真实 DOM
- 渲染函数被调用,生成虚拟 DOM 树。接下来,Vue 的渲染器会根据虚拟 DOM 树创建出真实的 DOM 元素,并将它们插入到页面中
- 更新阶段:差异化更新(渲染器将通过一个非常高效的“差异算法”(diffing 算法)对比新旧虚拟 DOM 树之间的差异。)
- 差异化比较
- 局部更新
五、模板 vs. 渲染函数
有没有想过:vue提供了模板,同时也提供了API给开发者,直接手写渲染函数。
都有模板了,为什么还提供渲染函数呢?
可能渲染函数更加灵活吧。
为什么vue默认推荐使用模板嘞?
模板更贴近实际的 HTML 结构
模板更容易进行静态分析
静态分析指的是在编译时对模板代码进行分析和优化,识别出可以提前处理的部分,而不需要等待运行时。这是 Vue 优化性能的一个关键步骤。
减少运行时成本:
相比直接手写渲染函数,模板编译器能够对模板进行深入分析,在生成渲染函数时避免不必要的复杂操作。
例如,Vue 会提取模板中不变的部分,将它们转换为常量,使得它们不需要每次更新时都重新计算。
六、Vue 编译器用来提高虚拟 DOM 运行时性能优化
静态提升
<div>
<div>aa</div> <!-- 需提升 -->
<div>bb</div> <!-- 需提升 -->
<div>{{ cc }}</div>
</div>
Vue 编译器会识别出 aa 和 bb 这两个 <div> 是完全静态的,它们不依赖任何动态数据。
它会将这部分静态内容从渲染函数中提取出来,转移到渲染函数外部,避免在每次渲染时都重新创建和比较这些节点。这样,渲染函数只需要关注 {{ cc }} 这部分动态内容。
这个过程被称为 静态提升,Vue 会将静态节点提升为常量,渲染器可以直接重用它们,而无需每次重新生成。这不仅提升了性能,还减少了内存消耗,因为 Vue 不再每次渲染时都创建新的虚拟 DOM 节点。
当静态内容非常多时,Vue 会进一步对这些内容进行压缩。它会把一系列连续的静态节点合并成一个“静态 vnode”(虚拟节点)。这个“静态 vnode”将包含这些节点的纯 HTML 字符串,并直接通过 innerHTML 来挂载。这意味着这部分静态内容不再通过 createElement 或类似方法动态创建,而是直接通过浏览器的 innerHTML 快速插入。
除了提升和压缩外,Vue 在初次挂载时还会缓存这些静态内容。如果这些静态内容在组件的后续渲染中需要被重复使用,Vue 会直接克隆这些已缓存的 DOM 节点,而不需要重新创建它们。这是通过 cloneNode() 方法实现的,它能有效避免重复的 DOM 操作,提升性能。
更新类型标记
<!-- 仅含 class 绑定 -->
<div :class="{ aa }"></div>
<!-- 仅含 id 和 value 绑定 -->
<input :id="id" :value="value">
<!-- 仅含文本子节点 -->
<div>{{ cc }}</div>
在为这些元素生成渲染函数时,Vue 在 vnode 创建调用中直接编码了每个元素所需的更新类型:
createElementVNode("div", {
class: _normalizeClass({ active: _ctx.aa })
}, null, 2 /* CLASS */)
这个2其实就是一个更新类型的标记而已。
当然是一对多的,一个元素对应n个标记。
运行的时候,渲染器使用位运算检查这些标记。
然后进行更新,vue可以在更新带有动态绑定的元素的时候做出最少的操作。
至此撒花~
后记
从这几个方面讲下来DOM的渲染机制,相信面试官对你的印象会大大加深。
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。