是否可以“缓冲”循环中发生的DOM更改(以提高性能)? [英] Is it possible to “buffer” DOM changes that happen in a loop (to increase performance)?

查看:80
本文介绍了是否可以“缓冲”循环中发生的DOM更改(以提高性能)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为清楚说明我的要求,这是我的示例(小提琴)。



我有一个〜500个随机名称的列表。我在顶部输入了一个实时搜索样式。在每个快捷键上,将使用输入值,并将列表中的每个项目与其匹配。隐藏不匹配的项目。



主观上,表现还不错,但效果不佳。如果您快速键入,则列表更新之前会有明显的暂停。我没有分析代码,但是瓶颈几乎可以肯定是对DOM的更改及其引起的重排。



我想知道是否有可能排队这些更改,仅在循环结束时实际应用它们。因此,这将是一次巨大的回流,而不是很多次。



在另一版本的小提琴,我使用了RegExp来使匹配和显示更加精美。即使我在此操作中使用了更多的DOM操作(添加/删除标签以使匹配突出显示),其性能感觉大致相同。我也曾尝试在CSS中添加可见/隐藏类,并仅将元素的 className 设置为该值,因为这样做的效果更好(搜索 javascript reflows& ;重画顽固性-我不能发布两个以上的链接),但是在我的测试中(Firefox 54),我发现它是 worse 。所以我不知道发生了什么。



我想我实际上是在问什么:如何使这段代码更快

解决方案

缓冲对DOM的更新没有任何意义,DOM本身在重排/渲染之前就已经可以了。 / p>

您要瞄准的是仅使用便宜交互对DOM做 less 更新,例如

500个元素是完全可行的,而您的第一个小提琴已经对我有所响应。在第二个中,我确定了一些问题区域和可能的改进:




  • innerText 不好。 确实很糟糕它会强制重排,因为它考虑了样式,并且不会返回不可见的文本(这样做也打破你的小提琴)。 改为使用 textContent

  • innerHTML 几乎一样糟糕,因为它需要调用HTML解析器。 500次有时(对于大块)可能比手动更新DOM的每个部分要快,但在这里不是。不必破坏并重新创建所有这些标签,而是将元素保留在DOM中。

  • 去抖动。您已经在执行此操作,但是您可能想使用 requestAnimationFrame 而不是很小的 setTimeout ,这样

  • 与DOM无关,但是新RegExp 也相当昂贵。您只需要调用一次,而不是为每个项目调用一次。

  • 每次函数都不要从DOM查询 listItems 被调用,但是像在 list search 一样,将数组缓存在函数外部。而且,您甚至可以做得更好:还要缓存它们的内容和样式对象,这样就不必通过DOM访问它们。



因此,一旦您修复了 快速骇客的方式来删除< b&s (如您自己记录的那样),大部分问题应该消失了。这是我的方法要点:

  var search = document.getElementById(’s); 
变量项= Array.from(document.getElementById('l')。children,function(li){
return {
text:li.textContent,
style:li .style,
之前:li.firstChild,//文本节点
匹配:li.appendChild(document.createElement( span))
.appendChild(document.createTextNode( )),
帖子:li.appendChild(document.createTextNode())
};
});

函数searchAction(){
var term = search.value;
var re = new RegExp(term,‘i’); //不区分大小写的

用于(项的{var,{text,style,pre,match,post}}){
var m = text.match(re);
if(m){
pre.nodeValue = text.slice(0,m.index);
match.nodeValue = m [0];
post.nodeValue = text.slice(m.index + m [0] .length);
show(style);
} else {
hide(style);
}
}
}

请参见更新了小提琴


To make it clear what I'm asking, here is my example (fiddle).

I have a list of ~500 random names. I have an input at the top that has live-style searching. On every keyup, the value of the input is taken, and every item in the list is matched against it. Items that don't match are hidden.

Subjectively, the performance is okay, but not great. If you type quickly there is a noticeable pause before the list updates. I haven't profiled the code, but the bottleneck is almost certainly the changes to the DOM and the reflows it causes.

I wonder if it's possible to "queue up" these changes and only actually apply them at the end of the loop. So it would be one giant reflow and not lots of little ones.

In another version of the fiddle, I used a RegExp to get more fancy with the matching and presentation. Even though I'm using more DOM manipulation in this one (adding/removing tags to enable match highlighting) the performance feels about the same. I did also try adding visible/hidden classes in CSS and just setting the elements' className to that because that is supposed to be better performing (search for javascript reflows & repaints stubbornella—I can't post more than 2 links) but in my testing (Firefox 54) I found it was worse. So I don't know what's going on there.

What I guess I'm actually asking is: how do I make this code faster?

解决方案

There's no point in buffering updates to the DOM, the DOM itself does that already just fine before reflowing/rerendering.

What you have to aim for are doing less updates to the DOM, using only cheap interactions, as few interactions as possible (Where "interactions" includes getters). Oh, and never use properties that force a reflow.

500 elements are quite doable, and your first fiddle is already quite responsive for me. In the second, I have identified a few problem zones and possible improvements:

  • innerText is bad. Really bad. It forces a reflow, as it takes into account styling and will not return invisible text (which also did break your fiddle). Use textContent instead.
  • innerHTML is nearly as bad, as it requires the HTML parser to be invoked. 500 times. That can sometimes (for large chunks) be faster than manually updating every part of the DOM, but not here. Instead of destroying and recreating all these tags, keep the elements in the DOM.
  • Debouncing. You're already doing this, but you might want to use requestAnimationFrame instead of a very small setTimeout, so that the DOM is updated only exactly once before it is rendered.
  • Not related to the DOM, but new RegExp is also rather expensive. You only need to call it once, not for every item.
  • Don't query the listItems from the DOM every time the function is called, but cache the array outside of the function like you do for list and search. And you can do even better: Also cache their contents and the style objects, so that you don't have to access them through the DOM.

So once you fix the "Quick hacky way to remove <b>s" (as you documented it yourself), most of the problems should be gone. Here's the gist of my approach:

var search = document.getElementById('s');
var items = Array.from(document.getElementById('l').children, function(li) {
  return {
    text: li.textContent,
    style: li.style,
    pre: li.firstChild, // the text node
    match: li.appendChild(document.createElement("span"))
             .appendChild(document.createTextNode("")),
    post: li.appendChild(document.createTextNode(""))
  };
});

function searchAction() {
  var term = search.value;
  var re = new RegExp(term, 'i'); // case insensitive

  for (var {text, style, pre, match, post} of items) {
    var m = text.match(re);
    if (m) {
      pre.nodeValue = text.slice(0, m.index);
      match.nodeValue = m[0];
      post.nodeValue = text.slice(m.index + m[0].length);
      show(style);
    } else {
      hide(style);
    }
  }
}

See updated fiddle.

这篇关于是否可以“缓冲”循环中发生的DOM更改(以提高性能)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆