前言
无论是Vue还是React,都推崇虚拟dom,社区也在推崇使用虚拟dom。我们通过操作虚拟dom能够避免开发者直接操作真实dom,在虚拟dom上应用各种更新后,由框架来统一完成对真实dom的操作。
一开始我也是按照这种先入为主的既定思想来做事的,认为操作虚拟dom在一定程度上可以帮助我们减轻操作真实dom带来的性能开销。一直以来都是带着疑惑的去相信,虚拟dom确实能高效的完成dom操作,但是我今天似乎不再这么认为了,以下是我做的几项简单的测试:
(PS:本文仅代表我个人观点,文中若有以偏概全的地方、或者描述得不恰当的地方,希望大家包容并指出)
实验环境
- Vue: 2.7.14
- React:17.0.2
- Chrome: 113.0.5672.64(正式版本) (64 位)
- Microsoft Edge:113.0.1774.42 (正式版本) (64 位)
- Firefox:113.0.1 (64 位)
由于是在Windows上实验的,就没有测试Safari
开始实验
测试代码
本文针对 “三种浏览器”,采用三种不同方式实现 “对dom节点添加 10000 个span元素” 进行简单测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>virtual-dom-test</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script crossorigin src="https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<script crossorigin src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
const app = document.querySelector('#app');
/* 普通js操作 */
for (let i = 0; i < 10000; i++) {
const span = document.createElement('span');
span.innerText = i;
app.appendChild(span);
}
/* Vue */
const vue = new Vue({
el: app,
template: `<div>
<span v-for="i in 10000">{{ i }}</span>
</div>`
})
/* React */
function App() {
return <div>{Array(10000).fill(0).map((_, i) => <span>{i}</span>)}</div>
}
ReactDOM.render(<App />, app);
</script>
</body>
</html>
Chrome
测试前先注释掉相关CDN以及Vue和React的测试代码,将script的type属性去掉或改为text/javascript
打开chrome的测试面板进行 performance 测试
原生js操作
耗时:
- js – 17ms
- style + layout – 71ms
Vue
- js – 53ms
- style + layout – 86ms
React
- js – 167ms
- style + layout – 83ms
Microsoft Edge
接下来轮到 Microsoft Edge 表演
原生js操作
耗时:
- js – 13ms
- style + layout – 109ms
Vue
- js – 53ms
- style + layout – 106ms
React
- js – 212ms
- style + layout – 102ms
Firefox
接下来轮到 Firefox 表演,这个火狐的分析面板属实有点难以看懂🤣
原生js操作
耗时:
- js – 14ms
- style + layout – 112ms
Vue
- js – 48ms
- style + layout – 8 + 28 + 76 = 112ms
React
- js – 120ms
- style + layout – 8 + 28 + 82 = 118ms
统计
累死我了,可算是测完了,以下为汇总数据
注意
需要注意的是,大多数浏览器为了减少重排重绘,采用了队列化修改和批量执行来优化整个过程,因此才能让以上数据在使用框架与不使用框架几乎一致
同时,获取布局信息的操作,例如:clientHeight、clientWidth、offsetHeight、offsetWidth、scrollTop等,会使队列刷新,立刻进行样式计算和布局计算,例如:
以下操作会耗费性能:
/* 普通js操作 */
for (let i = 0; i < 10; i++) {
const span = document.createElement('span');
span.innerText = i;
app.appendChild(span);
console.log(app.clientWidth);
}
可以见到,每轮循环浏览器都会进行重排,因此获取布局信息的操作要结合当前代码慎用,避免过早的render造成性能问题。
总结
经过以上测试发现,真正的 “样式计算 + 页面布局” 在三种操作中的耗时是几乎一致的
在浏览器队列化修改、批量更新策略下,他会将一帧中的所有dom操作收集起来,在js同步代码执行完成后统一处理。所以,虚拟dom并没有比操作真实dom快,只是把对真实dom的操作变成了对虚拟dom的操作,最后还是需要框架统一的去操作真实dom。反而,在内存里构建两颗虚拟树我觉得更消耗时间。
原文链接:https://juejin.cn/post/7235484890019692602 作者:WeilinerL