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?
Why not use a debounce function from underscore to limit the number of calls to get new data? I generally use 3-400 ms delay for search input fields. This will reduce the number of times that the dom has changes and will also prevent the flashing of search results if the user is a slow typist.
Find details of debounce here
Sometimes, thinking outside the box can be very rewarding...
CSS allows you to easily hide many elements with "display: none" and browsers are exceptionally well optimized when it comes to CSS.
So is there a way to apply this CSS rule to your searched term and let the browser do the job for you? (yes!)
Basically:
That's it, no more JS hurdle, only CSS doing it's job at lightning speed. Supported in all browsers and you only update 1 hidden element in the page (the "script" tag).
I also have updated your JS fiddle for the example.
As long as you do not query properties that trigger an immediate reflow the browser already batches the updates for you and only relayouts/repaints the page once your javascript is done. You should be able to verify this in the browser devtools.
But there is a trivial optimization for your code: avoid redundant DOM manipulations, especially for elements that are not visible anyway.
The else branch that gets applied for hidden items:
the toPlainText manipulates the DOM even the element will be hidden or already is hidden.
Many JS mvvm frameworks such as Reactjs and Vuejs has applied the 'virtual DOM mechanism'.It will batch all DOM updates in one event loop and save you from the penalty of frequent DOM operations.I think you can have a loot at that implementations.
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). UsetextContent
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.requestAnimationFrame
instead of a very smallsetTimeout
, so that the DOM is updated only exactly once before it is rendered.new RegExp
is also rather expensive. You only need to call it once, not for every item.listItems
from the DOM every time the function is called, but cache the array outside of the function like you do forlist
andsearch
. 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:See updated fiddle.