I have a doubt about DOM manipulation.
Whether dom-manipulation requires a refresh ? Or when did DOM manipulation requires refreshing? I see some of the sites keep loading while updating some of their parts.
Also,
How does react.js help to avoid this kind of problems while dealing with the front end development?
I have a doubt about DOM manipulation. Whether dom-manipulation requires a refresh ?
It depends on what you mean by "refresh." I can think of at least three possible things you could mean:
"Refresh" like pressing F5 on a page or hitting the reload button
"Refresh" like recalculate the positions of the elements; this is more correctly called "reflow"
"Refresh" as in repaint the elements on the screen ("repaint")
Reload
No, it doesn't, and in fact if you reload the page, the changes you've made to the DOM with your browser-based code will get wiped out. Here's a really simple example of DOM manipulation; no reloading is done until the end, and when it's done you can see that the changes made previously are wiped out (and then, since the code is also reloaded, we're starting from scratch, so it all starts over):
for (let i = 0; i < 5; ++i) {
delayedAdd(i);
}
function delayedAdd(value) {
setTimeout(() => {
// DOM manipulation
const p = document.createElement("p");
p.textContent = value;
document.body.appendChild(p);
if (value === 4) {
// Refresh after the last one -- wipes out what we've done
setTimeout(() => {
location.reload();
}, 800);
}
}, value * 800);
}
Reflow
Some DOM manipulations trigger reflow, yes; or more accurately, doing some things (which might be just getting, say, an element's current clientLeft property value) triggers reflow, and reflow may involve recalculating layout, which can be expensive. Code can cause reflow repeatedly, including causing the layout to be recalculated repeatedly, doing a single series of manipulations. This list from Paul Irish claims to be "What forces layout/reflow. The comprehensive list." and Paul Irish is a deeply-experienced person in this realm and well-regarded in the industry, so the list is likely to be accurate (though no one is perfect, and these things can sometimes change over time). The page also has some guidance on how to avoid reflow (or more accurately, how to avoid layout recalculation).
Here's an example of code causing unnecessary layout recalculation:
const elementLefts = [];
// This loop triggers layout recalculation 10 times
for (let n = 0; n < 10; ++n) {
const span = document.createElement("span");
span.textContent = n;
document.body.appendChild(span);
// Triggers layout recalcuation every time:
elementLefts.push(span.offsetLeft);
}
console.log(elementLefts.join(", "));
We can get that same information after the loop and only trigger a single layout recalculation:
const spans = [];
// This loop triggers layout recalculation 10 times
for (let n = 0; n < 10; ++n) {
const span = document.createElement("span");
span.textContent = n;
document.body.appendChild(span);
spans.push(span);
}
// Triggers one layout recalcuation, because nothing has changed between
// times we get `offsetLeft` from an element
const elementLefts = spans.map(span => span.offsetLeft);
console.log(elementLefts.join(", "));
Repaint
The browser repaints the screen all the time, typically 60 times per second (or even more if the system it's running on has a higher refresh rate) unless it's blocked by a long-running task on the UI thread (which is shared with the main JavaScript code). DOM manipulations don't cause this repaint (though changing the DOM may change what's painted, which might in turn prevent the browser reusing some painting information it had from last time).
Also, How does react.js help to avoid this kind of problems while dealing with the front end development?
They can help with reflows by minimizing layout recalculations by using the knowledge of what causes recalcs and avoiding doing the things that cause them in loops, etc. That said, they're not a magic bullet, and like everything else they have cons as well as pros.
Related
I have a page with JavaScript which takes a very long time to run. I have profiled the code using Firefox and following is the output.
As you can see I have moved the time consuming lines to a method _doStuff, which seems to do a lot of Graphic related things. Following is the content of the _doStuff method.
_doStuff:function(tds,colHeaderTds,mainAreaTds ){
for (i = 1; i < tds.length; i++) {
if (colHeaderTds[i].offsetWidth <= tds[i].offsetWidth) {
colHeaderTds[i].style.width = tds[i].offsetWidth + "px";
}
mainAreaTds[i].style.width = colHeaderTds[i].offsetWidth + "px";
}
},
I am assuming that the time consuming Graphics sections are due to setting the widths of the elements. Is this observation correct? And how should I go about optimizing the code so that it would take less time to load the page?
Every iteration of your loop JS changes your DOM tree and forces browser to repaint it.
The good practice is to make a copy of your element, modify it in the loop, and after loop change the .innerHTML of the former element.
More reading about repaints on the topic here
Since JavaScript is sequential (not counting async abilities), then why does it not "seem" to behave sequential as in this simplified example:
HTML:
<input type="button" value="Run" onclick="run()"/>
JS:
var btn = document.querySelector('input');
var run = function() {
console.clear();
console.log('Running...');
var then = Date.now();
btn.setAttribute('disabled', 'disabled');
// Button doesn't actually get disabled here!!????
var result = 0.0;
for (var i = 0; i < 1000000; i++) {
result = i * Math.random();
}
/*
* This intentionally long-running worthless for-loop
* runs for 600ms on my computer (just to exaggerate this issue),
* meanwhile the button is still not disabled
* (it actually has the active state on it still
* from when I originally clicked it,
* technically allowing the user to add other instances
* of this function call to the single-threaded JavaScript stack).
*/
btn.removeAttribute('disabled');
/*
* The button is enabled now,
* but it wasn't disabled for 600ms (99.99%+) of the time!
*/
console.log((Date.now() - then) + ' Milliseconds');
};
Finally, what would cause the disabled attribute not take effect until after the for-loop execution has happened? It's visually verifiable by simply commenting out the remove attribute line.
I should note that there is no need for a delayed callback, promise, or anything asynchronous; however, the only work around I found was to surround the for-loop and remaining lines in a zero delayed setTimeout callback which puts it in a new stack...but really?, setTimeout for something that should work essentially line-by-line?
What's really going on here and why isn't the setAttribute happening before the for loop runs?
For efficiency reasons, the browser does not immediately layout and display every single change you make to the DOM instantly right when the change is made. In many cases, DOM updates are collected into a batch and then updated all at once at some later time (like when the current thread of JS finishes).
This is done because if a piece of Javascript is making multiple changes to the DOM, it is very inefficient to relayout the document and then repaint each change as it occurs and much more efficient to wait until the Javascript finishes executing and then repaint all the changes at once.
This is a browser-specific optimization scheme so every browser makes their own implementation decisions on exactly when to repaint a given change and there are some events that can cause/force a repaint. As far as I know, this is not an ECMAScript-specified behavior, just a performance optimization that each browser implements.
There are some DOM properties that require a finished layout before the property is accurate. Accessing these properties via Javascript (even just reading them) will force the browser to do a layout of any pending DOM changes and will usually also cause a repaint. One such property is .offsetHeight and there are others (though all in this category have the same effect).
For example, you can probably cause a repaint by changing this:
btn.setAttribute('disabled', 'disabled');
to this:
btn.setAttribute('disabled', 'disabled');
// read the offsetHeight to force a relayout and hopefully a repaint
var x = btn.offsetHeight;
This Google search for "force browser repaint" contains quite a few articles on this topic if you want to read about it further.
In cases where the browser still won't repaint, the other work-arounds are to hide, then show some element (this causes layout to be dirty) or to use a setTimeout(fn, 1); where you continue the rest of your code in the setTimeout callback - thus allowing the browser a chance to "breathe" and do a repaint because it thinks your current thread of Javascript execution is done.
For example, you could implement the setTimeout workaround like this:
var btn = document.querySelector('input');
var run = function() {
console.clear();
console.log('Running...');
var then = Date.now();
btn.setAttribute('disabled', 'disabled');
// allow a repaint here before the long-running task
setTimeout(function() {
var result = 0.0;
for (var i = 0; i < 1000000; i++) {
result = i * Math.random();
}
/*
* This intentionally long-running worthless for-loop
* runs for 600ms on my computer (just to exaggerate this issue),
* meanwhile the button is still not disabled
* (it actually has the active state on it still
* from when I originally clicked it,
* technically allowing the user to add other instances
* of this function call to the single-threaded JavaScript stack).
*/
btn.removeAttribute('disabled');
/*
* The button is enabled now,
* but it wasn't disabled for 600ms (99.99%+) of the time!
*/
console.log((Date.now() - then) + ' Milliseconds');
}, 0);
};
The browser doesn't render changes to the DOM until the function
returns. - #Barmar
Per #Barmar's comments and a lot of additional reading on the subject, I'll include a summary referring to my example:
JavaScript is single threaded, so only one process at a time can occur
Rendering (repaint & reflow) is a separate/visual process that the browser performs so it comes after the function finishes to avoid the potentially heavy CPU/GPU calculations that would cause performance/visual problems if rendered on the fly
Summarized another way is this quote from http://javascript.info/tutorial/events-and-timing-depth#javascript-execution-and-rendering
In most browsers, rendering and JavaScript use single event queue. It means that while JavaScript is running, no rendering occurs.
To explain it another way, I'll use the setTimeout "hack" I mentioned in my question:
Clicking the "run" button puts my function in the stack/queue of things for the browser to accomplish
Seeing the "disabled" attribute, the browser then adds a rendering process to the stack/queue of tasks.
If we instead add a setTimeout to the heavy part of the function, the setTimeout (by design) pulls it out of the current flow and adds it to the end of the stack/queue. This means the initial lines of code will run, then the rendering of the disabled attribute, then the long-running for-loop code; all in the order of the stack as it was queued up.
Additional resources and explanations concerning the above:
Event Loop
Painting
Reflow
My web page creates a lot of DOM elements at once in a (batch) tight loop, depending on data fed by my Comet web server.
I tried several methods to create those elements. Basically it boils down to either (1):
var container = $('#selector');
for (...) container.append('<html code of the element>');
or (2):
var html = '';
for (...) html += '<html code of the element>';
$('#selector').append(html);
or (3):
var html = [];
for (...) html.push('<html code of the element>');
$('#selector').append(html.join(''));
Performance-wise, (1) is absolutely awful (3s per batch on a desktop computer, up to 5mn on a Galaxy Note fondleslab), and (2) and (3) are roughly equivalent (300ms on desktop, 1.5s on fondleslab). Those timings are for about 4000 elements, which is about 1/4 of what I expect in production and this is not acceptable since I should be handle this amount of data (15k elements) in under 1s, even on fondleslab.
The very fact that (2) and (3) have the same performance makes me think that I'm hitting the infamous "naively concatenating strings uselessly reallocates and copies lots of memory" problem (even though I'd expect join() to be smarter than that). [edit: after looking more closely into it, it happens that I was misled about that, the problem is more on the rendering side -- thanks DanC]
In C++ I'd just go with std::string::reserve() and operator += to avoid the useless reallocations, but I have no idea how to do that in Javascript.
Any idea how to improve the performance further? Or at least point me to ways to identify the bottleneck (even though I'm pretty sure it's the string concatenations). I'm certainly no Javascript guru...
Thanks for reading me.
For what it's worth, that huge number of elements is because I'm drawing a (mostly real-time) graph using DIV's. I'm well aware of Canvas but my app has to be compatible with old browsers so unfortunately it's not an option. :(
Using DOM methods, building and appending 12000 elements clocks in around 55ms on my dual-core MacBook.
document.getElementById('foo').addEventListener('click', function () {
build();
}, false);
function build() {
console.time('build');
var fragment = document.createDocumentFragment();
for ( var e = 0; e < 12000; e++ ) {
var el = document.createElement('div');
el.appendChild(document.createTextNode(e));
fragment.appendChild(el);
}
document.querySelectorAll('body')[0].appendChild(fragment);
console.timeEnd('build')
}
Fiddle
Resig on document.createDocumentFragment
This is not a solution to the performance problem, but only a way to ensure the UI loop is free to handle other requests.
You could try something like this:
var container = $('#selector');
for (...) setTimeout(function() {container.append('<html code of the element>') };
To be slightly more performant, I would actually call setTimeout after every x iterations after building up a larger string. And, not having tried this myself, I am not sure if the ordering of setTimeout calls will be preserved. If not, then you can do something more like this:
var arrayOfStrings = 'each element is a batch of 100 or so elements html';
function processNext(arr, i) {
container.append(arr[i]);
if (i < arr.length) {
setTimeout(function() { processNext(arr, i+1); });
}
}
processNext(arrayOfStrings, 0);
Not pretty, but would ensure the UI is not locked up while the DOM is manipulated.
I have 2000 rows of data as follows
<div class="rr cf">
<span>VLKN DR EXP</span>
<span>01046</span>
<span>VELANKANNI</span>
<span>20:30</span>
<span>DADAR</span>
<span>10:00</span>
</div>
On a button click I am checking for text within them and updating the display of each row to block or none. The code that does this is
$('.rr').each(function(){
this.style.display="block";
});
var nodes = $(".rr");
for(var i=0;i < nodes.length; i++) {
// if data found
nodes.get(i).style.display="block";
// else
nodes.get(i).style.display="none";
}
This seems to be possibly very slow. I get chrome alert box as to kill the page.
Any ideas? what optimization can I do here?
Local Variables and Loops
Another simple way to improve the performance of a loop is to
decrement the iterator toward 0 rather than incrementing toward the
total length. Making this simple change can result in savings of up to
50% off the original execution time, depending on the complexity of
each iteration.
Taken from: http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
Try saving the nodes.length as a local variable so that the loop doesn't have to compute it each time.
Also, you can store nodes.get(i) into a local variable to save some time if you are accessing that data a lot.
If the order isn't important, consider decrementing your for loop towards 0.
jQuery's each() loop is a bit slower than looping through the set yourself as well. You can see here that there is a clear difference.
Very simple example
You'll see that in my example, I've condensed the loop into a while loop:
var nodes = $(".rr span");
var i = nodes.length;
while(i--){
if(i%2 === 0){
nodes.get(i).style.color = "blue";}
}
Notice that the while loop decrements i through each iteration. This way when i = 0, the loop will exit, because while(0) evaluates to false.
"Chunking" the Array
The chunk() function is designed to process an array in small chunks
(hence the name), and accepts three arguments: a “to do” list of
items, the function to process each item, and an optional context
variable for setting the value of this within the process() function.
A timer is used to delay the processing of each item (100ms in this
case, but feel free to alter for your specific use). Each time
through, the first item in the array is removed and passed to the
process() function. If there’s still items left to process, another
timer is used to repeat the process.
Have a look at Nick Zakas's chunk method defined here, if you need to run the loop in sections to reduce the chance of crashing the browser:
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Using createDocumentFragment()
Since the document fragment is in memory and not part of the main DOM
tree, appending children to it does not cause page reflow (computation
of element's position and geometry). Consequently, using document
fragments often results in better performance.
DocumentFragment are supported in all browsers, even Internet Explorer
6, so there is no reason to not use them.
Reflow is the process by which the geometry of the layout engine's
formatting objects are computed.
Since you are changing the display property of these elements iteratively, the page mush 'repaint' the window for each change. If you use createDocumentFragment and make all the changes there, then push those to the DOM, you drastically reduce the amount of repainting necessary.
Firstly, where are the delays occurring - in the jquery code, or the data check? If it is the jquery, you could try detaching the data container element (ie the html element that contains all the .rr divs) from the DOM, make your changes, and then re-attach it. This will stop the browser re-processing the DOM after each change.
I would try
1) set display of the common parent element of all those divs, to "none"
2) loop through divs, setting display as appropriate
3) set parent element display back to block
I believe this will help because it gives the browser the opportunity to aggregate the rendering updates, instead of forcing it to fully complete each single time you change the display property. The visibility of all the child node are irrelevant if the parent isnt displayed, so the browser no longer has a need to render a change in each child until the parent becomes visible again.
ALso, I fail to see the purpose of you first looping through them all and setting them all to block before you loop again and set them to their intended value.
Don't use jQuery here, jQuery will just slow things down here.
var elements = document.getElementsByClassName('rr'),
len = elements.length;
for(var i = 0; i < len; i++)
{
var ele = elements[i];
if(ele.innerHTML.search(/01046/) != -1)
ele.style.display = "none";
}
This should be much faster.
I'm also having performance problems while looping through roughly 1500 items.
As you might have guessed, the loop itself isn't the bottleneck. It's the operation you do within it that's the problem.
So, what I migrated the load using setTimeout. Not the prettiest of solutions but it makes the browser responsive between the updates.
var _timeout_ = 0;
for(var i=0;i < nodes.length; i++)
{
setTimeout(
(function(i)
{
return function()
{
if(stuff)
{
nodes.get(i).style.display="block";
}
else
{
nodes.get(i).style.display="none";
}
}
})(i),
_timeout_
);
_timeout_ += 4;
}
This will delay every update with 4 milliseconds, if the operation takes longer, the browser will become unresponsive. If the operation takes only 2 milisecond on your slowest browser, you can set it to 3, etc. just play around with it.
The while statement in this function runs too slow (prevents page load for 4-5 seconds) in IE/firefox, but fast in safari...
It's measuring pixel width of text on a page and truncating until text reaches ideal width:
function constrain(text, ideal_width){
$('.temp_item').html(text);
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
var original = text.length;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
$('.temp_item').html(smaller_text);
item_width = $('span.temp_item').width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Any way to improve performance? How would I convert this to a bubble-sort function?
Thanks!
move the calls to $() outside of the loop, and store its result in a temporary variable. Running that function is going to be the slowest thing in your code, aside from the call to .html().
They work very very hard on making the selector engines in libraries fast, but it's still dog slow compared to normal javascript operations (like looking up a variable in the local scope) because it has to interact with the dom. Especially if you're using a class selector like that, jquery has to loop through basically every element in the document looking at each class attribute and running a regex on it. Every go round the loop! Get as much of that stuff out of your tight loops as you can. Webkit runs it fast because it has .getElementsByClassName while the other browsers don't. (yet).
Instead of removing one character at time until you find the ideal width, you could use a binary search.
I see that the problem is that you are constantly modifying the DOM in the loop, by setting the html of the temp_item, and then re reading the width.
I don't know the context of your problem, but trying to adjust the layout by measuring the rendered elements is not a good practice from my point of view.
Maybe you could approach the problem from a different angle. Truncating to a fixed width is common.
Other possibility (hack?) if dont have choices, could be to use the overflow css property of the container element and put the … in other element next to the text. Though i recommend you to rethink the need of solving the problem the way you are intending.
Hugo
Other than the suggestion by Breton, another possibility to speed up your algorithm would be to use a binary search on the text length. Currently you are decrementing the length by one character at a time - this is O(N) in the length of the string. Instead, use a search which will be O(log(N)).
Roughly speaking, something like this:
function constrain(text, ideal_width){
...
var temp_item = $('.temp_item');
var span_temp_item = $('span.temp_item');
var text_len_lower = 0;
var text_len_higher = smaller_text.length;
while (true) {
if (item_width > ideal)
{
// make smaller to the mean of "lower" and this
text_len_higher = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_lower)/2));
}
else
{
if (smaller_text.length>=text_len_higher) break;
// make larger to the mean of "higher" and this
text_len_lower = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_higher)/2));
}
temp_item.html(smaller_text);
item_width = span_temp_item.width();
}
...
}
One thing to note is that each time you add something to the DOM, or change the html in a node, the page has to redraw itself, which is an expensive operation. Moving any HTML updates outside of a loop might help speed things up quite a bit.
As other have mentioned, you could move the calls to $() to outside the loop. You can create a reference to the element, then just call the methods on it within the loop as 1800 INFORMATION mentioned.
If you use Firefox with the Firebug plugin, there's a great way of profiling the code to see what's taking the longest time. Just click profile under the first tab, do your action, then click profile again. It'll show a table with the time it took for each part of your code. Chances are you'll see a lot of things in the list that are in your js framework library; but you can isolate that as well with a little trial and error.