从新认识 Virtual DOM

那个争议开端

这件事还要从 2013 年那个秋天提及。javascript

这实际上很是快,主要是由于大多数DOM操做每每很慢。DOM上有不少性能工做,但大多数DOM操做都会丢帧。html

2013 React Pete Hunt

对!就是这张图,这张图把你们引入了 DOM 操做是昂贵且慢的,Virtual DOM 是快速的思惟里。前端

6 年后的今天,React 已经风靡全球,Virtual DOM 也受到了你们的承认,国产之星 VUE 也使用了 Virtual DOMjava

那么问题来了,Virtual DOM 真的快吗?Virtual DOM 的意义究竟是什么?咱们为何要使用 Virtual DOMreact

咱们都据说直接更新文档对象模型(DOM)效率低且速度慢。可是,咱们中不多有人真的有数据支持它。关于React虚拟DOM的讨论是,它是一种更有效的方式来更新 Web 应用程序中的视图,但咱们不多有人知道为何以及这种效率是否会致使更快的页面渲染时间。git

抛开使用 React 的其余好处,例如单向数据绑定和组件,我将讨论 Virtual DOM 究竟是什么,以及它是否可以证实 React 比其余UI库更合理(或者根本没有UI库) 。github

咱们为何须要 UI 库

咱们为何须要 UI 库呢?算法

我敢确定,如今的前端界,很大一部分人离开了三大框架以后就不知道该怎么办了,他们可能理所固然的认为视图和数据是绑定的(VUE),或者直接使用 setState 来更新视图(React)。npm

有了 UI 库以后,咱们能够直接数据与视图绑定,而不须要再操做 DOM浏览器

咱们为何不想操做 DOM

这里不会详细的讲 DOM,只会粗略带过。

DOM表明文档对象模型,是结构化文本的抽象。对于Web开发人员,此文本是HTML代码,DOM简称为*HTML DOMHTML元素成为DOM中的节点*。

HTML DOM提供了一个用于遍历和修改节点的接口(API)。它包含像getElementById或的方法removeChild。咱们一般使用JavaScript语言来处理DOM,由于......好吧,没人知道为何:)。

所以,每当咱们想要动态地更改网页的内容时,咱们都会修改DOM

var item = document.getElementById("myLI");
item.parentNode.removeChild(item);
复制代码

document是根节点的抽象getElementByIdparentNode并且removeChild是来自HTML DOM API的方法。

那么问题来了,因为HTML DOM始终是树形结构,咱们能够很容易地遍历每一个节点,可是现在Web APP的当下,DOM树愈来愈大,咱们须要不停的修改大量的DOM树。这是真正使人痛苦的地方。

咱们一般是如下一个流程来更新 DOM

  1. 遍历(或者使用 id)树找到相关的节点
  2. 在有必要时更新这个节点

这明显有几个问题:

  1. 很难管理。找一个节点并分析上下文的关系,耗时耗力,一不当心喜提bug
  2. 效率极低。

为何更新 DOM 很慢

更新DOM并不慢,就像更新任何JavaScript对象同样。

那到底是什么让更新真正的DOM变慢?

是绘制。

布局过程当中,绘制占用了大部分时间。

结合下图,以及此文章,你会明白,更新 DOM 的真正问题是屏幕的绘制。

img

负责在浏览器屏幕上显示或呈现网页的渲染引擎解析HTML页面以建立DOM。它还解析CSS并将CSS应用于HTML,从而建立渲染树,此过程称为**attachment**。

因此,当咱们这样作时

document.getElementById('elementId').innerHTML="New Value"
复制代码

发生如下事情:

  1. 浏览器必须解析HTML
  2. 它删除了elementId 的子元素
  3. 使用"New Value"更新DOM
  4. 从新计算父和子的CSS
  5. 更新布局,即每一个元素在屏幕上的精确坐标
  6. 遍历渲染树并在浏览器显示上绘制它

从新计算CSS和更改布局使用复杂的算法,它们会影响性能。

所以,更新真正的DOM并不只仅涉及更新DOM,而是涉及许多其余过程。

此外,上述每一个步骤都针对真实DOM的每次更新运行,即若是咱们更新真实DOM 10次,则上述步骤中的每个将重复10次。这就是为何更新 DOM 很慢的缘由。

神奇的 Virtual DOM

首先 , Virtual DOM不是由 React发明的,但React使用它并免费提供。

因为 DOM 操做的复杂性,Virtual DOM被创造了出来,他以一个虚拟树的状态,存储在内存中,再映射到真实的 DOM,每次更新,都是虚拟树的对比,再将差别部分进行更新,并反映到真实 DOM 上去,这样咱们就减小了对真实 DOM 的操做。

React中更新虚拟DOM的速度更快,由于React使用了

  1. 高效的diff算法
  2. 批量batching操做
  3. 仅有效地更新子树
  4. 使用可观察(observable)而不是脏检查来检测更改

AngularJS使用脏检查来查找已更改的模型。这个脏检查过程在指定时间后循环运行。随着应用程序的增加,检查整个模型会下降性能,从而使应用程序变慢。

每当调用setState()方法时,ReactJS都会从头开始建立整个Virtual DOM。建立整棵树很是快,所以不会影响性能。

在任何给定时间,ReactJS维护两个Virtual DOM,一个具备更新的状态Virtual DOM,另外一个具备先前的状态Virtual DOM

使用diff算法比较Virtual DOM以找到并更新至Real DOM

让咱们举个栗子,首先咱们 state.subject 的值是 world

<div>
  <div id="header">
    <h1>Hello, {{state.subject}}!</h1>
    <p>How are you today?</p>
  </div>
</div>
复制代码

解析后的 Virtual DOM 能够表示为:

{
  tag: 'div',
  children: [
    {
      tag: 'div',
      attributes: {
        id: 'header'
      },
      children: [
        {
          tag: 'h1',
          children: 'Hello, World!'
        },
        {
          tag: 'p',
          children: 'How are you today?'
        }
      ]
    }
  ]
}
复制代码

如今, state.subject的值改变为Mom,那么渲染出来的 Virtual DOM为:

{
  tag: 'div',
  children: [
    {
      tag: 'div',
      attributes: {
        id: 'header'
      },
      children: [
        {
          tag: 'h1',
          children: 'Hello, Mom!'
        },
        {
          tag: 'p',
          children: 'How are you today?'
        }
      ]
    }
  ]
}
复制代码

经过 diff 算法以后,肯定只更新的了 h1 这个元素,那么将更新的元素再映射到 DOM 上即完成了这次的更新。

至于 batchingdiff 算法 ,内容量较大,须要另开一篇博客讲,目前能搜到的讲解也很多,你们能够去搜搜。

Virtual DOM 真的快过直接操做 DOM 吗

关于Virtual DOM的每一篇文章和文章都会指出,虽然今天的JavaScript引擎速度很是快,但读取和写入浏览器的DOM的速度很慢。

这不彻底正确。DOM很快。添加和删除DOM节点并不比在JavaScript对象上设置属性慢得多。这只是一个简单的操做。

例以下面一个例子:

这是一个使用原生 DOM 渲染的方式:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello JavaScript!</title>

</head>
<body>
<div id="example"></div>
<script> document.getElementById("example").innerHTML = "<h1>Hello, world!</h1>"; </script>
</body>
</html>
复制代码

这是一个使用 React 实现的方式:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="build/react.js"></script>
    <script src="build/react-dom.js"></script>
</script>
</head>
<body>
<div id="example"></div>
<script type="text/babel"> ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); </script>
</body>
</html>
复制代码

使用原生须要渲染的时间:

Load Graph

使用 React 须要渲染的时间:

Load Graph

咋一看,原生渲染速度大大快于 React

可是咱们忽略了一个问题,就是页面数据量不多,这样操做,在一个大型列表全部数据都变了的状况下,还算是合理,可是,当只有一行数据发生变化时,它也须要重置整个 innerHTML,这时候显然就形成了大量浪费。

比较 innerHTMLVirtual DOM 的重绘过程以下:

  • innerHTML: render html string ==> 从新建立全部 DOM 元素
  • Virtual DOM: render Virtual DOM ==> diff ==> 必要的 DOM 更新

DOM 操做比起来,js 计算是很是廉价的。Virtual DOM render + diff 显然比渲染 html 字符串要慢,可是,它依然是纯 js 层面的计算,比起后面的 DOM 操做来讲,依然好了太多。

浏览器在DOM更改时必须执行的布局。每次DOM更改时,浏览器都须要从新计算CSS,进行布局并从新绘制网页,这须要大量时间。

浏览器制造商不断努力缩短从新绘制屏幕所需的时间,能够作的最大的事情是最小化和批量DOM更改。

这种减小和批处理DOM更改的策略,采用另外一个抽象级别,是ReactVirtual DOM背后的理念。

最后

React 历来没有说过 “React 比原生操做 DOM 快”。React给咱们的保证是,在不须要手动优化的状况下,它依然能够给咱们提供过得去的性能。

React掩盖了底层的 DOM 操做,能够用更声明式的方式来描述咱们目的,从而让代码更容易维护。

借鉴了知乎上的回答:没有任何框架能够比纯手动的优化 DOM 操做更快,由于框架的 DOM 操做层须要应对任何上层 API 可能产生的操做,它的实现必须是普适的。针对任何一个 benchmark,我均可以写出比任何框架更快的手动优化,可是那有什么意义呢?在构建一个实际应用的时候,你难道为每个地方都去作手动优化吗?出于可维护性的考虑,这显然不可能。

最后推广一下我基于 Taro 框架写的组件库:MP-ColorUI

能够顺手 star 一下我就很开心啦,谢谢你们。

点这里是文档

点这里是 GitHub 地址

相关文章
相关标签/搜索