原生 js 操作 dom 比框架快

540 阅读4分钟

1. 原生 DOM 操作 vs 通过框架封装操作

任何框架操作 dom 其底层都是操作原生的真实 dom,所以纯原生 dom 操作比任何框架都快。框架封装的意义是为了提高代码的可维护性,用性能换取维护性。

2. 对 React 的 Virtual DOM 的误解

React 的基本思维模式是每次有变动就重新渲染整个应用。如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。在一个列表所有数据都变了的情况下,重置 innerHTML 其实还算合理, 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时显然就有大量的资源浪费。
比较 innerHTML 和 Virtual DOM 的重绘性能消耗:

  • innerHTML: render html string O(template size) + 重新创建所有 DOM 元素 O(DOM size)

  • Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但它是纯 js 层面的计算,比起 DOM 操作来说,便宜了太多。
innerHTML 的计算量: js 计算和 DOM 操作都是和整个界面的大小相关;
Virtual DOM 的计算量: 只有 js 计算和界面大小相关,DOM 操作是和数据的变化量相关。与 DOM 操作相比,js 计算是极其便宜的。
Virtual DOM意义:不管你的数据变化多少,每次重绘的性能都可以接受;

3. MVVM vs. Virtual DOM

其他 MVVM 系框架比如 Angular, Knockout 以及 Vue、Avalon 采用的都是数据绑定:通过 Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的。
MVVM 的性能也根据变动检测的实现原理有所不同:Angular 的脏检查使得任何变动都有固定的 O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依赖收集,在 js 和 DOM 层面都是 O(change)

  • 脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)
  • 依赖收集:重新收集依赖 O(data change) + 必要 DOM 更新 O(DOM change)

Angular 最不效率的地方在于任何小变动都有的和 watcher 数量相关的性能代价。
依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小量更新的时候几乎可以忽略,但在数据量大的时候会产生一定的消耗。

MVVM 渲染列表:每一行都有自己的数据作用域,所以通常每一行有一个对应的 ViewModel 实例,或者是一个利用原型继承的 "scope" 对象,但也有一定的代价。所以,MVVM 列表渲染的初始化几乎一定比 React 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要昂贵很多。
所有 MVVM 实现的一个共同问题:列表渲染的数据源变动时,尤其是当数据是全新的对象时,如何有效地复用已经创建的 ViewModel 实例和 DOM 元素。假如没有任何复用方面的优化,MVVM 实际上需要销毁之前的所有实例,重新创建所有实例,再进行渲染!
React 的变动检查由于是 DOM 结构层面的,即使是全新的数据,只要最后渲染结果没变,那么就不需要做无用功。

(Angular 和 Vue 都提供了列表重绘的优化机制:“提示” 框架如何有效地复用实例和 DOM 元素。比如数据库里的同一个对象,在两次前端 API 调用里面会成为不同的对象,但是它们依然有一样的 uid。这时候你就可以提示 track by uid 来让 Angular 知道,这两个对象其实是同一份数据。那么原来这份数据对应的实例和 DOM 元素都可以复用,只需要更新变动了的部分。或者,你也可以直接 track by $index 来进行 “原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果 angular 实现加上 track by $index 的话,后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和 Vue 用了 track by $index 以后都比 React 快。)=> 不理解

4. 性能优化场合

  • 初始渲染:Virtual DOM > 脏检查 >= 依赖收集
  • 小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) > Virtual DOM 无优化
  • 大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化

5. 总结虚拟 dom 意义

  1. 为函数式 UI 编程打开了大门;
  2. 将js对象渲染到浏览器 dom 以外的环境实现跨平台编程,如ReactNative

参考:www.zhihu.com/question/31…