如何对DOM元素进行排序,从而触发最少量的回流?

时间:2017-07-26 08:03:20

标签: javascript html algorithm sorting dom

我有以下例子。

<div class="parent">
  <div data-id="5"></div>
  <div data-id="2"></div>
  <div data-id="3"></div>
  <div data-id="1"></div>
  <div data-id="4"></div>
</div>

如果我想按升序(1,2,3,4,5)订购这些div。我通常会做一个循环并将div附加到parent div。但这意味着我总是对dom进行5次更改(无论div的顺序如何),每个div都有一次。

但是,您可以使用.insertBefore()方法正确排序div,仅 2次更改

5,2,3,1,4
Insert 1 before 2
5,1,2,3,4
Insert 5 behind 4 (using .insertBefore() and .nextSibling)
1,2,3,4,5 

问题1 通过仅对DOM进行2次更改,我假设回流次数较少,使得&#39; 2更改&#39;排序操作比“更改”排序操作更快。这是对的吗?

问题2 只有插入1 before 25 behind 4才能找到哪种方法/算法?

问题3(奖金)这是否会优化&#39;因为物品的数量增加,算法仍然会更快?范围10 - 100 - 1.000 - 10.000 - 100.000

也许澄清一下:正在寻找以最佳方式找出订单(1,2,3,4,5)的方法。在某个时刻,我知道订单,但我想再次比较div的顺序,然后THEN找出最少的操作量

3 个答案:

答案 0 :(得分:5)

对浏览器的功能更有信心。

  1. 浏览器在一次单一同步执行JavaScript序列时执行DOM操作。当您通过访问要求DOM是最新的DOM属性/方法来明确地(有时是在不知不觉中)请求重排时,会发生异常,例如, innerWidthoffsetTopgetBoundingClientRect。有关详细信息,请参阅Rendering: Repaint, Reflow, Restyle 删除DOM节点不是必需的。将它们添加到新创建的DocumentFragment,它们将自动从DOM中的当前位置移除。

  2. 浏览器,更具体地说是JS引擎,已经知道并使用最智能的排序算法。除特定情况外,您无需了解排序操作的内部机制。只需使用Array.prototype.sort,必要时提供自定义功能,并观察魔法发生。

答案 1 :(得分:2)

从DOM中删除所有节点,并在您的代码中对它们进行排序后按正确的顺序替换它们。

<强> HTML:

<div id="wrapper">
  <div id="par" class="parent">
    <div id="5">5</div>
    <div id="2">2</div>
    <div id="3">3</div>
    <div id="1">1</div>
    <div id="4">4</div>
  </div>
</div>

<强>使用Javascript:

var clone = document.getElementById("par")
  .cloneNode(true)
var childs = [].slice.call(clone.children);
var sorted = childs.sort(function(a, b) {
  return parseInt(a.id) - parseInt(b.id)
})
var frag = document.createDocumentFragment();
sorted.forEach(function(el) {
  frag.appendChild(el)
})

var wrapper = document.getElementById("wrapper");
wrapper.removeChild(document.getElementById("par"));
wrapper.appendChild(frag);

说明: 单个DOM操作比排序算法中的算法步骤重。

如果数字变大,最智能的排序算法需要O(n log n)次节点数量。这意味着n * log(n)DOM操作。在人类语言中,这只是节点数量的几倍。

但是如果你只是删除所有节点,然后以正确的顺序再次添加它们,那么最坏的情况下会得到n + 1个DOM操作。可能你可以将所有节点添加到一起,你最终会得到一个接近2 / O(1)的数字,但我并不专注于现代浏览器实际完成的速度,所以让#39保守地留在n + 1。

现在谈谈数字:

假设您有5个节点。在这种情况下,一切都很好,数字很小,但为了你自己的平安,以及你和你的同事的未来和平,你想写一个清晰的算法。因此,删除所有节点并按正确的顺序替换它们。

现在说你有1000个节点。在这种情况下,排序将需要大约10,000次操作(n * log n = 1000 * 10)**。如果将每个节点分别放在那里,则将有10,000个DOM操作。

相反,如果您只是从DOM中删除节点,在javascript代码中对它们进行排序,然后将它们重新放入,那么您将只有1000个DOM操作。这是非常保守的,因为我觉得它实际上只需要2个DOM操作:一次删除所有节点,一次以正确的顺序添加所有节点***

**我宁愿给出难以理解的数字,只是为了弄清楚这一切。基于1024的基数2,即10 ***这可能是真正的区别所在。如果有人知道,请评论/编辑!

答案 2 :(得分:0)

请看一下这段代码。

基本上它从dom中移除容器,应用排序,将其重新插入dom。

通常这一切都可以在一帧重绘中发生,因此不会影响页面/滚动问题的长度

+function() {
  /**
   * Finding thing wrapping class
   */
  var $con = $('#container');
  /**
   * getting the parent of the wrapping class so we can reinsert when we're done.
   */ 
  var $parent = $con.parent();
  /**
   * Removing container from dom so we can sort in speed
   */   
  $con.remove();
  
  /**
   * Getting the things we need from container that we need to sort
   */
  var $tosort = $con.find('.thing');
  
  /**
   * Apply sorting algorything, get a newly sorted list
   */
  var $neworder = $tosort.sort(function(a,b){
     return parseInt(a.innerText) > parseInt(b.innerText);
  });
  /**
   * Simply append the newly ordered list. They are the same objects, not clones so they'll fit into the right order.
   */
  $con.append($neworder);
  /**
   * Reinsert container into the dom.
   */
  $parent.append($con);
}()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrap">
  <div id="container">
    <div class="thing">
      4
    </div>
    <div class="thing">
      3
    </div>
    <div class="thing">
      1
    </div>
    <div class="thing">
      5
    </div>
    <div class="thing">
      2
    </div>
  </div>
</div>