Skip to content

如何改进这段代码

Posted on:2023年9月29日 at 14:49

如何改进这段代码

给出以下代码:

<!doctype html>
<html lang="zh-Hans-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>如何改进这段代码-未优化版本</title>
  </head>
  <body>
    <ul id="myList"></ul>

    <script>
      let items = [...Array(50000).keys()].map(i => `Item ${i}`);

      let ul = document.getElementById("myList");

      for (let item of items) {
        let li = document.createElement("li");
        li.textContent = item;
        ul.appendChild(li);
      }
    </script>
  </body>
</html>

应该如何优化这段代码呢? 在传统的思维中,当我们有一个向DOM中追加(append)一些DOM节点的需求,最先联想到的API就是:ParentNode.appendChild,此API可以将一个节点附加到指定父节点的子节点列表的末尾处,但是如果频繁对DOM进行更新操作(通常是在循环中),由于每次循环都会插入一个新的节点,会导致浏览器回流(英文:reflow,即重新布局)一次。

什么是回流?

根据生成的渲染树,进行回流(Reflow),得到节点的几何信息(位置,大小)。回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。大部分的回流将导致页面的重新渲染。 回流必定会发生重绘,重绘不一定会引发回流。

由于重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。为了减少发生次数,我们可以合并多次对DOM和样式的修改,然后一次处理掉。

CSS

<div>
  <a> <span></span> </a>
</div>
<style>
  span {
    color: red;
  }
  div > a > span {
    color: red;
  }
</style>

对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。

JavaScript

避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

想了解哪些属性和方法当在 JavaScript 中调用时,将触发浏览器同步样式计算和布局。参考此篇文章。

根据以上关于浏览器渲染性能方面的介绍,考虑一下方式优化这段代码:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>如何改进这段代码-优化版本</title>
  </head>
  <body>
    <ul id="myList"></ul>

    <script>
      let items = [...Array(50000).keys()].map(i => `Item ${i}`);

      let ul = document.getElementById("myList");
      let fragment = document.createDocumentFragment();

      for (let item of items) {
        let li = document.createElement("li");
        li.textContent = item;
        fragment.appendChild(li);
      }

      ul.appendChild(fragment);
    </script>
  </body>
</html>

关于DocumentFragments(en-us)

DocumentFragments (en-US) 是 DOM 节点。它们不是主 DOM 树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

参考链接: