在当下最流行的两个前端框架都存在 Virtual DOM 的前提下, 渐渐比较多的听到类似“使用 Virtual DOM 有什么优势?” 的面试题,但一直没有太在意。直到今天在写一个文档时,突让想到要把“为什么需要 Virtual DOM ?”也写进去,待我流畅的写好答案,略一思索——漏洞百出!也不知道是接纳了哪方的知识,让我一直有能轻松回答这个问题的错觉, 其实对于这个问题我是缺乏思考的。
你或许还不清楚我想说什么,但请耐下心来,先来看看网络上关于此问题的一些见解:
虚拟DOM同样也是操作DOM,为啥说它快?[1]
Virtual Dom 的优势在哪里?[2]
Virtual Dom的优势[3]
上面是从 Google 搜索到的三个平台中的分析摘选,总结下来大概四点:
它们的理解正确吗?本文测试数据都基于 Chrome 86.0.4240.198
有人认为操作 Virtual DOM 速度很快?Virtual DOM 是一个用来描述 DOM(注意,并不一定一一对应)的 Javascript 对象,Javascript 操作 Javascript 对象自然是快的。但 Virtual DOM 仍然需要调用 DOM API 去生成真实的 DOM ,而你其实是可以直接调用它们的,所有就有一个很有意思结论,正数再小也不可能比零还小——Virtual DOM 很快,但这并不是它的优势,因你本可以选择不使用 Virtual DOM 。除了速度不是优势,Virtual DOM 还有个最大的问题——额外的内存占用,以 Vue 的 Virtual DOM 对象为例,100W 个空的 Virtual DOM(Vue) 会占用 110M 内存。内存占用截图:
测试代码:
let creatVNode = function(type) {
return {
__v_isVNode: true,
SKIP: true,
type,
props: null,
key: null,
ref: null,
scopeId: 0,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag: 0,
patchFlag: 0,
dynamicProps: null,
dynamicChildren: null,
appContext: null
}
}
let counts = 1000000
let list = []
let start = performance.now()
// 创建 VNode(Vue)
// 10000: 1120k
for (let i = 0; i < counts; i++) {
list.push(creatVNode('div'))
}
// 创建 DOM
// 10000: 320k
// for (let i = 0; i < counts; i++) {
// list.push(document.createElement('div'))
// }
console.log(performance.now() - start)
_令人意外的是 100W 个空的 DOM 对象只占用 45M 内存,不清楚在 DOM 属性明显更多的情况下 Chrome 是如何优化的,或则是 Dev Tools 存在问题,希望有人能替我解惑。_你看 Virtual DOM 不但执行快没有用,还增加了大量的内存消耗,所以我们说它快自然是有问题的,因为没有 Virtual DOM 时更快。
也有人认为 Virtual DOM 能减少页面的 relayout 和 repaint ?通常有两个原因来支撑这个观点:
patch
方法批量操作 DOM ,批量操作就不会导致过程中出现无意义的回流和重绘。第一个观点看着很有道理,但有个问题很难解释:浏览器的 UI 线程在什么时候去执行回流和重绘?要知道现代浏览器在设计上为了避免高复杂度,Javascript 线程和 UI 线程是互斥的,即如果浏览器要在 Javascript 执行期间触发 relayout/repaint 则必须先挂起 Javascript 线程,这是个连我都觉得蠢的设计,显然不会出现在各大浏览器身上。事实上也确实如此,无论你在一次事件循环中调用多少次的 DOM API ,浏览器也只会触发一次回流与重绘(如果需要),并且如果多次调用并没有修改 DOM 状态,那么回流与重绘一次都不会发生。Timeline 截图(没有回流和重绘发生):
测试代码:
<body>
<div class="app"></div>
<script> let counts = 1000
let $app = document.querySelector('.app')
setTimeout(() => {
for (let i = 0; i < counts; i++) {
$app.innerHTML = 'aaaa'
$app.style = 'margin-top: 100px'
$app.innerHTML = ''
$app.style = ''
}
}, 1000) </script>
</body>
第二个观点是比较有意思的,虽然看了上面的分析,你应该也知道它是错的,批量操作并不能减少回流与重绘,因为它们本身就只会触发一次。但我还是要列出来证明一下,因为这是我们当下众多前端的一个固有思维,我在准备写这篇文章前问了一下众神交流群的朋友们,他们几乎都掉进了这个认知陷阱中,认为批量操作会减少回流与重绘。批量操作并不能减少回流与重绘,原因也和上文一致,Javascript 是单线程且与 UI 线程互斥,所以直接放测试数据:Javascript 执行耗时(数据取3次平均值):
Layout 耗时(数据取3次平均值):
测试代码:
<body>
<div class="app"></div>
<script> let counts = 1000
let $app = document.querySelector('.app')
let start = performance.now()
// 单独操作
// for (let i = 0; i < counts; i++) {
// let node = document.createTextNode(`${i}, `)
// $app.append(node)
// }
// 批量操作
let $tempContainer = document.createElement('div')
for (let i = 0; i < counts; i++) {
let node = document.createTextNode('node,')
$tempContainer.append(node)
}
$app.append($tempContainer)
console.log(performance.now() - start) </script>
</body>
可以看到的是,批量处理和单次处理再 Layout 期间耗时是几乎一致的,虽然在 script 执行阶段还是存在一定的性能优势(大概 30%),但大抵上只要你用好 DOM 操作,批量或不批量带来的性能影响是很小的( 10W 次调用多损耗 27ms )。题外话:这里提出一个问题,为什么在 script 执行阶段还是存在一定的性能差距?答案会在晚些时候公布(等我看完这部分逻辑)
严格来说 diff 算法和 Virtual DOM 是两个独立的东西,二者互相之间也没有充分必要的关联,比如 svelte[4] 没有 Virtual DOM 也有其自己的 diff 算法。但由于前端框架存在 Virtual DOM 就总有 diff 算法,并且使用了 Virtual DOM 对 diff 算法也有两个助力:
diff 算法能减少 DOM API 调用,显然是存在设计和性能优势的,而由于 Virtual DOM 的存在,diff 算法可以更方便且更强大,所以我认同这是 Virtual DOM 的优势,但不能用“Virtual DOM 有 diff 算法”这样的表述。
上文提到的 svelte 没有 Virtual DOM ,但一样可以实现服务端渲染,这说明跨平台并不依赖于 Virtual DOM 。其实只要 Javascript 框架有实现平台 API 分发机制,就能在不同平台执行不同的渲染方法,即拥有跨平台能力。这个能力的根本,是 Javascript 代码能低代价地在各个平台运行(得利于浏览器在各个平台的普及和 NodeJS),也就是常说的 Javascript 的优势之一是跨平台。所以把跨平台当做 Virtual DOM 的优势,其实是不正确的,但我们或许应该去思考下他们为什么会这么认为。我的想法,可能是这两个原因:
本文从互联网上摘选了部分对开发者对 Virtual DOM 优点的认知,也从现实生活中了解到一些误解,总结为 Virtual DOM 的四个“优势”,并分别对这四个“优势”进行了单独分析或举证。最终我们识别了几个关于 Virtual DOM 优势误区:
我们也提到了 Virtual DOM 真正的优点是其抽象能力和常驻内存的特性,让框架能更容易实现更强大的 diff 算法,缺点是增加了框架复杂度,也占用了更多的内存。
参考资料
[1]虚拟DOM同样也是操作DOM,为啥说它快? (https://segmentfault.com/q/1010000010303981)[2]Virtual Dom 的优势在哪里?( https://github.com/RomanHc/blog/issues/20) [3]Virtual Dom的优势 (https://juejin.cn/post/6844904179715014669) [4]svelte(https://github.com/sveltejs/svelte)
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8