别再说虚拟 DOM 快了,要被打脸的

简介: 我经常听到有人在群里,或者在社区里说的一个很严重的错误,那就是说 React 的 Virtual Dom 是以快出名的,比原生 DOM 快多了,啥啥啥的,每次都一两句话说不清楚,所以下次有谁再说 React 是以快出名的,你就把这篇文章丢给他,下面进入正题。

640 (8).jpg

如果你觉得虚拟 DOM 很快,那么这篇文章可能就是你所缺少的


我经常听到有人在群里,或者在社区里说的一个很严重的错误,那就是说 React 的 Virtual Dom 是以快出名的,比原生 DOM 快多了,啥啥啥的,每次都一两句话说不清楚,所以下次有谁再说 React 是以快出名的,你就把这篇文章丢给他,下面进入正题。


在过去的几年里,你一直在跟踪 JavaScript 社区的发展,你至少听说过 Virtual DOM(React,Vue.js 2,Riot.js,Angular 2等等)。他们承诺(或者更确切地说,他们的宣传)更快的渲染界面,特别是更新,减少麻烦。你很快的上手了使用虚拟DOM的应用程序,这很好。几个月后,您的应用程序现在变得越来越复杂,你可能从用户交互到屏幕更新只需要一两秒钟的更新。你可能会想,这东西很神奇,应该会比 jQuery 快,但是实际上不是这个样子的。


虽然我同意虚拟 DOM 为我们提供了很多便利,但我将解释为什么我认为根据定义,更快的渲染和更快的更新是不正确的。要付出代价,其利益并不是大多数人想象或至少希望的。


要阅读本文,您需要熟悉DOM。理想情况下,您至少可以使用 DOM API。如果你只使用 DOM API 构建东西,你可能不需要这篇文章,但我仍然希望你阅读它并在评论中留下一点评语。


渲染和更新


让我们来看看手动执行 DOM 节点的创建和更新的鸟瞰图。这对于理解虚拟DOM如何工作以及它解决了哪些问题非常重要。


在谈论 JavaScript Web 应用程序时,用户界面的更改通过 DOM 操作发生。这个过程分为两个阶段:


  1. JS 部分:定义 JavaScript 世界中的变化


  1. DOM 部分:使用 DOM API 函数和属性执行更改


性能是根据整个过程的速度来衡量的,但了解每部分的速度也很重要,以便了解要优化的内容。


有两种方法可以创建和更新DOM树的各个部分。


①字符串方式创建


使用字符串既快速又简单,但在更新方面并不是非常精细。对于字符串,JS部分是它如此之快的原因。您可以在几毫秒内创建一段代表5000个节点的HTML。这是一个例子:


const userList = document.getElementById("user-list");
// JS 部分
const html = users.map(function (user) {
  return `
    <div id="${user.id}" class=”user”>
      <h2 class="header">${user.firstName} ${user.lastName}</h2>
      <p class="email"><a href=”mailto:${user.email}”>EMAIL</a></p>
      <p class="avg-grade">Average grade: ${user.avgGrade}</p>
      <p class="enrolled">Enrolled: ${user.enrolled}</p>
    </div>
  `
}).join("");
// DOM 部分
userList.innerHTML = html;


我提到使用这种方法时存在局限性。请考虑以下示例:


const search = document.getElementById("search");
search.innerHTML = `<input class="search" type="text" value="foo">`;
// Change value to "bar"?
search.innerHTML = `<input class="search" type="text" value="bar">`;


虽然看起来上面的内容很简单,但它实际上并不起作用。当我们运行上面的代码时,原始<input>元素被替换而不是更新,例如,如果用户有焦点的字段,他们将失去焦点。


②使用 DOM 对象


创建和更新 DOM 树的另一种方法是使用 DOM 对象。就你必须编写的代码而言,这种方法非常冗长,而且总体来说它也慢得多。


让我们使用这个方法重写用户列表示例:


const userList = document.getElementById(“user-list”);
// JS part 
const = document.createDocumentFragment(); 
users.forEach(function(user){ 
  const div = document.createElement(“div”); 
  div.id = user.id; 
  div.className =“user”; 
  const header = document.createElement(“h2”); 
  h2 .className =“header”; 
  h2.appendChild(
    document.createTextNode(`$ {user.firstName} $ {user.lastName}`)
  ); 
  // .... 
  frag.appendChild(div); 
});
// DOM部分
userList.innerHTML =“”; 
userList.appendChild(FRAG);


这看起来不太好,但它仍然是创建DOM节点的有效方法。它还有一个优点,即我们能够将它与第三方库(如D3)混合使用,以执行 HTML 字符串不易处理的事情。在真正的优势,虽然是执行粒度更新现有的树时:


const search = document.getElementById(“search”); 
search.innerHTML =`<input class ="search" type ="text"value ="foo">`; 
//将值更改为“bar”?
search.querySelector("input")。value ="bar";


这次我们结合快速方便的字符串 HTML 方法来创建初始 UI,然后我们使用 DOM 操作方法来更新 value 属性。不像我们第一次这样做,<input>现在没有被替换,所以它不会像第一个例子那样引起 UX 故障。


进入虚拟DOM


让我们回到输入示例的第一个版本:


const search = document.getElementById("search");
search.innerHTML = `<input class="search" type="text" value="foo">`;
// Change value to "bar"?
search.innerHTML = `<input class="search" type="text" value="bar">`;


如果我们参数化值部分,它将如下所示:


const search = document.getElementById("search");
const renderInput = function (value) {
  search.innerHTML = `<input class="search" type="text" value="${value}">`;
};
renderInput("foo");
// Change value to "bar"?
renderInput("bar");


好吧,新 renderInput() 功能肯定看起来很酷,但我们已经知道这不是好方法。


如果我们有一些骚操作可以让我们继续使用类似的东西,但同时弄清楚我们想要做什么并做正确的事情呢?第二次 renderInput() 被调用,我们只更新 value 属性,所以只更新该属性而不是重新渲染整个属性<input>


我们说过创建和更新 DOM 树的整个过程分为两个阶段。使用虚拟 DOM,DOM 阶段应该尽可能高效,代价是在 JS 阶段完成的额外工作。这项额外的工作会做 diff(不要以为 js 计算就不花费代价),因此它的另一个名称将是开销。根据定义,虚拟 DOM 比精心设计的手动更新慢,但它为我们提供了一个更方便的 API 来创建 UI。


虚拟DOM比精心设计的手动更新慢。


为什么有些开发人员认为Virtual DOM更快


在虚拟DOM(尤其是React)的早期,传播了一个神话,即虚拟 DOM 使 DOM 快速更新。正如我们在前面的章节中看到的那样,这在技术上是不可行的。DOM 更新就是它们的原因,并且没有任何魔法可以使它更快:它必须在浏览器的本机代码中进行优化。


640 (9).jpg


可以看到 React 主页里面没有提到性能,而是开发人员的便利性。


React 的基本思维模式是每次有变动就整个重新渲染整个应用。如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作… 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。


您仍然可以看到比较各种虚拟 DOM 实现的基准测试,并且一些措辞会误导新开发人员认为虚拟 DOM 是当今事实上的标准,并且不值得对其他技术进行基准测试。然而,有一些基准可以将它与其他技术进行比较,例如 Aerotwist 的 React +性能文章,它描绘了虚拟 DOM 在宏观方案中所处位置的更真实的画面。


我们得到了什么?这值得么?


虚拟DOM最终是一种执行 DOM 更新的循环方式。但是,它打开了通向有趣架构的大门,例如将视图视为状态函数,或者编写和组合视图组件。虚拟 DOM 带来了很多好东西,尽管疯狂的性能水平不是其中之一。您可以将其视为 Python 或 PHP 中的编码与 C 中的编码之间的差异。我们以性能为代价获得更多的开发人员工具。换句话说,这是一种权衡。


另一方面,开发人员的时间丢失也是一些实现方面的事情。虚拟 DOM 试图弄清楚它需要执行哪些更改的部分是由人类实现的,因此它并不总是万无一失。有时你必须介入。在某些情况下,无法进行干预。对于绝对性能至关重要的事情,它甚至可能不是一种选择。


衡量您的表现并根据硬数据来决定。


最重要的是,虚拟DOM只是您可以使用的工具之一。衡量您的表现并根据硬数据来决定。数据绑定仍然非常可行,我们已经看到您也可以手动完成所有操作。它绝对不是万能的,因此没有必要与虚拟DOM结合。


结论


React 厉害的地方并不是说它比 DOM 快,而是说不管你数据怎么变化,我都可以以最小的代价来进行更新 DOM。 方法就是我在内存里面用心的数据刷新一个虚拟 DOM 树,然后新旧 DOM 进行比较,找出差异,再更新到 DOM 树上。


这就是所谓的 diff 算法,虽然说 diff 算法号称算法复杂度 O(n) 可以得到最小操作结果,但实际上 DOM 树很大的时候,遍历两棵树进行各种对比还是有性能损耗的,特别是我在顶层 setState 一个简单的数据,你就要整棵树 walk 一遍,而真实中我可以一句 jQuery 就搞定,所以就有了 shouldComponentUpdate 这种东西。


框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。


针对每一个点,都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。

目录
相关文章
|
6月前
|
JavaScript 前端开发 编译器
说说你对虚拟 DOM 的理解?
说说你对虚拟 DOM 的理解?
56 0
|
6月前
|
JavaScript 开发者 UED
虚拟DOM的原理
虚拟DOM的原理
68 1
|
6月前
|
JavaScript 前端开发 算法
MVVM模型,虚拟DOM和diff算法
1.MVVM是前端开发领域当中非常流行的开发思想。(一种架构模式)目前前端的大部分主流框架都实现了这个MVVM思想,例如Vue,React等2.虽然Vue没有完全遵循MVVM模型,但是Vue的设计也受到了它的启发。Vue框架基本上也是符合MVVM思想的 3.MVVM模型当中尝到了Model和View进行了分离,为什么要分离?
|
6月前
|
前端开发 JavaScript 算法
深入理解前端框架:解析 React 的虚拟 DOM
虚拟 DOM 是 React 前端框架的核心概念之一。本文将深入探讨虚拟 DOM 的原理和应用,帮助读者更好地理解 React 框架的工作机制,并学习如何高效地使用虚拟 DOM 进行前端开发。
101 1
|
6月前
|
JavaScript 前端开发 算法
js开发:请解释什么是虚拟DOM(virtual DOM),以及它在React中的应用。
虚拟DOM是React等前端框架的关键技术,它以轻量级JavaScript对象树形式抽象表示实际DOM。当状态改变,React不直接操作DOM,而是先构建新虚拟DOM树。通过高效diff算法比较新旧树,找到最小变更集,仅更新必要部分,提高DOM操作效率,降低性能损耗。虚拟DOM的抽象特性还支持跨平台应用,如React Native。总之,虚拟DOM优化了状态变化时的DOM更新,提升性能和用户体验。
91 0
|
5月前
|
JavaScript 前端开发 算法
虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。
【6月更文挑战第27天】虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。当状态改变,React不直接修改DOM,而是先构建新的虚拟DOM树。通过 diff 算法比较新旧树,找到最小变更,仅更新必要部分,提高性能,避免频繁DOM操作。虚拟DOM还支持跨平台应用,如React Native。它优化了更新流程,简化开发,并提升了用户体验。
43 1
|
6月前
|
JavaScript 算法 前端开发
虚拟Dom
虚拟Dom
|
6月前
|
JavaScript 前端开发 算法
深入理解虚拟DOM:原理、优势与实践
深入理解虚拟DOM:原理、优势与实践
363 0
|
6月前
|
JavaScript 前端开发 算法
深入探讨前端框架Vue.js中的虚拟DOM机制
本文将深入探讨前端框架Vue.js中的虚拟DOM机制,分析其原理、优势以及在实际开发中的应用场景,帮助读者更好地理解Vue.js框架的核心特性。
|
6月前
|
JavaScript 算法 前端开发
Vue的虚拟DOM:Vue虚拟DOM的工作原理
【4月更文挑战第24天】Vue的虚拟DOM提升渲染性能,通过创建JavaScript对象树(虚拟DOM树)来跟踪DOM变化。当状态改变,Vue用新的虚拟DOM树与旧树对比(diff算法),找到最小DOM操作集合来更新真实DOM。优化策略包括减少状态变化、使用key属性和简化组件结构。理解虚拟DOM工作原理有助于Vue的性能优化。