虚拟DOM真的能提高性能吗?

784 阅读6分钟

前言

JQuery盛行的时代,那时还没有前后端分离的概念,PHP被称为世界上最好的语言。随着Chrome的兴起,传统操作DOM的编程思维已经不适用于中大型项目,于是前端也迎来了一系列变革

之后各种MV*框架应运而生,其中AngularJS, React, Vue被称为三驾马车,MMVM使得前端的开发效率大幅提升。下面我们去深入理解一下虚拟DOM

原生DOM & 虚拟DOM

原生DOM与虚拟DOM可以归结为:直接操作DOM通过框架封装操作DOM的区别

  1. 直接操作DOM: 以JQuery为代表;

  2. 通过框架封装操作DOM: 以Vue和React为代表

那么问题来了:直接操作DOM和Virtual DOM哪一个更快?

很多人都对虚拟DOM有一个误解认为使用React比直接操作DOM更快

其实这是不对的,框架封装操作DOM需要对DOM作生成,对比,更新操作。虽说js计算消耗的性能很少,但是总归会耗费一定的性能

React从来没有说过"React比原生操作DOM快"。只是很多人对这句话产生了误解,原文如下:

React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.

React will efficiently update, 其实就是diff算法最好的阐释

结论: 直接操作DOM是最快的

Virtual DOM

为什么直接DOM那么快,JQuery时代却成为历史了呢?

原因在于,前端发展越来越快,页面功能越来越来越复杂,页面效果越来越炫酷,直接操作DOM的代码会难以阅读和维护,于是数据视图分离,数据驱动视图便应运而生了,VDom开始唱主角了

Virtual DOM在不需要手动优化的情况下,依然可以给你提供过得去的性能,更增加了可维护性。用最小的代价来更新DOM,提升效率,解放了生产力,被越来越多的开发者所接受

声明式代码 vs 命令式代码

什么是命令式?

命令式代码的特点是关注过程

/*

1. 获取id为app的标签
2. 写入文本内容为Hello World!
3. 添加绑定点击事件
4. 当点击时弹出提示:ok
*/

const div = document.querySelector('#app') // 获取标签

div.innerText = 'Hello World!' // 设置文本内容

div.addEventListener('click', () => { alert('ok') }) // 绑定点击事件

可以看到,自然语言描述能够与代码产生一一对应的关系,代码本身描述的是“做事的过程”,这符合我们的逻辑直觉

声明式

声明式框架更加关注结果

<div @click="() => alert('ok')">hello world</div>

我们提供的是一个“结果”,至于如何实现这个“结果”,我们并不关心,这就像我们在告诉 Vue.js:“嘿,Vue.js,给我一个div,文本内容是 Hello World!,并且有个点击后弹框的效果,你帮我搞定吧。”

Vue.js 帮我们封装了过程。因此,但是Vue.js的内部实现一定是命令式的,暴露给用户的却更加声明式

性能比较

声明式代码的性能不优于命令式代码的性能, 毕竟框架本身就是封装了命令式代码才实现了面向用户的声明式。

虚拟DOM操作和原生DOM操作有异曲同工之处

Vue

虽然Vue和React都是Virtual DOM,但接下来如果不是特别说明,都是代指Vue

Vue.js的虚拟DOM

image.png

虚拟DOM最核心的部分就是Patch。Patch也叫做Patching算法,在Vue中使用Patching算法去计算更新的节点,从而减少DOM操作,进而提升性能。

通过Patch我们可以对比新旧两个虚拟DOM,对发生变化的节点进行更新操作。

虚拟DOM VS 性能优化

很多人喜欢把数据的更新和Virtual DOM进行强行绑定: 认为数据的更新必须要使用虚拟DOM。其实,他们两者没有任何一点关系。任何技术的出现,抛开场景去谈,都是无源之水,无本之木。在这里很多人去说: 操作真实的DOM太浪费性能,虚拟DOM的出现,不正是去解决性能问题吗?

那么,虚拟DOM的出现,真的是因为性能问题吗?

谈性能的时候,要分场合的。

  1. 小量数据更新: 当页面的渲染数据高达几万条,但只需要修改某一条数据时,Virtual DOM的Diff比对算法,能够避免不必要的DOM操作,节省开销
  2. 大量数据更新: 当页面的渲染数据高达几万条, 但是更新的数据却非常大,甚至全量更新Virtual DOM无法进行优化, 加剧了性能的浪费

在这里引用一下尤大的原话:

不要天真地以为Virtual DOM就是快,diff不是免费的,batching么MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来Virtual DOM真正的价值从来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门;2) 可以渲染到 DOM 以外的backend,比如ReactNative。

image.png www.zhihu.com/question/31…

为什么要引入虚拟DOM

  1. 组件的高度抽象化: 使用函数式编程,通过代码组合和复用,开发更加高效,更加拓展和维护
  2. 可以更好的实现 SSR,同构渲染
  3. 框架跨平台: Virtual DOM是对渲染内容的一层抽象描述,这就使得视图层和渲染层做了解耦。同一份代码可以运行到多端,从而实现了跨平台的可能性

代码调试

<el-button @click="setFirst">设置第一项</el-button>
<el-button @click="reverse">反转</el-button>
<div v-for="item in arr" :key="item">{{item}}</div>
            
created() {
    for (let i = 0; i < 100; i++) {
        this.arr.push(i)
    }
},
methods: {
    reverse() {
        this.arr = this.arr.reverse()
    },
    setFirst() {
        this.arr[0] = -1
        this.$set(this.arr)
    }
},

然后我们去调试Vue.js源码

...
// 7073行源码
return function patch(oldVnode, vnode, hydrating, removeOnly) {
   // 7088行
   if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // patch existing root node
      console.log('patchVnode-------')
      console.time()
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
      console.timeEnd()
   }
}

image.png

测试结果

  1. 数据量为100时:

点击:设置第一项: image.png

点击:反转: image.png

速度可以忽略不计

  1. 数据量为1000时

点击:设置第一项: image.png

点击:反转: image.png

速度可以忽略不计

  1. 数据量为一万时

点击:设置第一项: image.png

点击:反转: image.png

当全量更新数据量达到上万级别时,Virtual DOM的性能开销成倍的加大

  1. 数据量为十万

点击:设置第一项:

image.png

点击:反转: image.png

当全量更新数据量达到十万级别时,Virtual DOM的性能呈现几十倍的加大

当数据量达到百万级别,可想而知,Virtual DOM的性能压力有多大 当然,百万级别的数据,肯定不会一次性展示,肯定需要单独做优化,这是后话

总结: diff不是免费的,Virtual DOM的出现也不是为了性能而产生的

参考资料

  1. 网上都说操作真实DOM慢,但测试结果却比 React 更快,为什么
  2. Vue采用虚拟DOM的目的是什么?
  3. 理解虚拟DOM
  4. 你真的知道为什么需要虚拟DOM吗?
  5. vue.js的设计与实现