jquery/js too slow while operating over 2000 rows - javascript

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.

Related

Whether dom-manipulation requires a refresh?

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.

Force browser to show a dynamically generated content block immediately without waiting to show all blocks at one time

I am using jQuery to add a list of dynamically generated blocks of content to a page. Generating a block of content takes some time in my case, and so I would like the browser to display a generated block immediately when added without the browser showing ALL generated blocks at one time. Here is how my Javascript code flows in concept:
var results = $('#results');
var i;
for (i = 0; i < 20; i++) {
var block = Handlebars-generated-content;
results.append(block);
var more = = Handlebars-generated-content;
block.append(more);
}
When my page is loaded, the browser is blank initially, a visitor has to wait, and then all added blocks are displayed at once. I hope the browser can display one block when it is added, so that a visitor can see partial content immediately without waiting longer.
The reason for this is you're using a for loop, which is synchronous; It will run the entire loop, and all subsequent content generations, in a single "frame".
Instead, you should use an "asynchronous loop". This involves making a function and calling it recursively using a zero-millisecond setTimeout() after each generation. The timeout gives the browser an opportunity to render so your element will show up sooner.
var items = ['first', 'second', 'third', 'fourth', 'fifth'];
var index = 0;
function asyncLoop() {
// get our current item based on the index
var item = items[index];
// do your work here
$('#results').append('<ul>' + item + '</ul>');
// increase the index by 1
index++;
// keep looping if the index isn't at the end yet
if (index < items.length) {
// call ourselves using a timeout to give the browser a change to render
// intentionally set to 500ms to make this more obvious, set it to 0 normally
setTimeout(asyncLoop, 500);
}
}
// start the loop
asyncLoop();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="results">
</ul>
The only downside to this approach is that doing a single generation per timeout may introduce extra delay between operations since timeouts usually default to minimum of around 4ms on most browsers. To cope with this, you may want to do a small batch of operations per "loop" to optimize the process.
It's also worth noting that this is not threading, just an async loop. The processing will always execute in order, the async aspect simply means it won't process everything all at once.

How can I optimize the following JavaScript which sets the CSS properties of Elements

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

Simple JS For-loop confusion

In this example, I want to remove all the pararaphes. But as you can see, the for-loop below didn't remove them all, it still left the first paragraph for me.
http://codepen.io/vinhnghi223/pen/jnkzh
article=document.getElementsByTagName("article")[0];
for (i=0; i<article.childNodes.length; i++) {
article.removeChild(article.lastChild);
}
However, if I change the code to i<4 (which is less than article.childNodes.length), which returns 5, it works.
I got why article.childNodes.length returns 5. It just confuses me because I think if it works with 4 then it should work with 5.
It's because the .childNodes collection updates with every removal. So the .length is shrinking on every iteration, while i is growing. Since you're removing the .lastChild, you're going to end up meeting in the middle, and so the first half won't be removed.
When you hard code the original length, you eliminate the problem.
If your intent is to remove all the children, then just have the condition check to see if a .lastChild exists.
while (article.lastChild) {
article.removeChild(article.lastChild);
}
"Live" node lists like this can be tricky, so you always need to be careful when modifying their elements in a loop.
That's because you are changing the collection that you are looping through. You are looping from zero and up, and at the same time you are removing child nodes so that the upper limit of the loop is getting lower. In the middle your loop counter will meat the shrinking number of child nodes, and the loop ends.
For that loop to work, you would need to preserve the initial number of child nodes:
var cnt = article.childNodes.length;
for (i = 0; i < cnt; i++) {
article.removeChild(article.lastChild);
}
However, you can just loop while there are any child nodes left:
while (article.childNodes.length> 0) {
article.removeChild(article.lastChild);
}

Disabling the long-running-script message in Internet Explorer

I have a JavaScript function that contains a for loop that iterates so many times.
After calling this function, the IE browser displays this message:
Stop running this script?
A script on this page is causing your web browser to run slowly.
If it continues to run, your computer might become unresponsive.
How can I fix this?
is there anyway I can disable this message from IE?
This message displays when Internet Explorer reaches the maximum number of synchronous instructions for a piece of JavaScript. The default maximum is 5,000,000 instructions, you can increase this number on a single machine by editing the registry.
Internet Explorer now tracks the total number of executed script statements and resets the value each time that a new script execution is started, such as from a timeout or from an event handler, for the current page with the script engine. Internet Explorer displays a "long-running script" dialog box when that value is over a threshold amount.
The only way to solve the problem for all users that might be viewing your page is to break up the number of iterations your loop performs using timers, or refactor your code so that it doesn't need to process as many instructions.
Breaking up a loop with timers is relatively straightforward:
var i=0;
(function () {
for (; i < 6000000; i++) {
/*
Normal processing here
*/
// Every 100,000 iterations, take a break
if ( i > 0 && i % 100000 == 0) {
// Manually increment `i` because we break
i++;
// Set a timer for the next iteration
window.setTimeout(arguments.callee);
break;
}
}
})();
The unresponsive script dialog box shows when some javascript thread takes too long too complete. Editing the registry could work, but you would have to do it on all client machines. You could use a "recursive closure" as follows to alleviate the problem. It's just a coding structure in which allows you to take a long running for loop and change it into something that does some work, and keeps track where it left off, yielding to the browser, then continuing where it left off until we are done.
Figure 1, Add this Utility Class RepeatingOperation to your javascript file. You will not need to change this code:
RepeatingOperation = function(op, yieldEveryIteration) {
//keeps count of how many times we have run heavytask()
//before we need to temporally check back with the browser.
var count = 0;
this.step = function() {
//Each time we run heavytask(), increment the count. When count
//is bigger than the yieldEveryIteration limit, pass control back
//to browser and instruct the browser to immediately call op() so
//we can pick up where we left off. Repeat until we are done.
if (++count >= yieldEveryIteration) {
count = 0;
//pass control back to the browser, and in 1 millisecond,
//have the browser call the op() function.
setTimeout(function() { op(); }, 1, [])
//The following return statement halts this thread, it gives
//the browser a sigh of relief, your long-running javascript
//loop has ended (even though technically we havn't yet).
//The browser decides there is no need to alarm the user of
//an unresponsive javascript process.
return;
}
op();
};
};
Figure 2, The following code represents your code that is causing the 'stop running this script' dialog because it takes so long to complete:
process10000HeavyTasks = function() {
var len = 10000;
for (var i = len - 1; i >= 0; i--) {
heavytask(); //heavytask() can be run about 20 times before
//an 'unresponsive script' dialog appears.
//If heavytask() is run more than 20 times in one
//javascript thread, the browser informs the user that
//an unresponsive script needs to be dealt with.
//This is where we need to terminate this long running
//thread, instruct the browser not to panic on an unresponsive
//script, and tell it to call us right back to pick up
//where we left off.
}
}
Figure 3. The following code is the fix for the problematic code in Figure 2. Notice the for loop is replaced with a recursive closure which passes control back to the browser every 10 iterations of heavytask()
process10000HeavyTasks = function() {
var global_i = 10000; //initialize your 'for loop stepper' (i) here.
var repeater = new this.RepeatingOperation(function() {
heavytask();
if (--global_i >= 0){ //Your for loop conditional goes here.
repeater.step(); //while we still have items to process,
//run the next iteration of the loop.
}
else {
alert("we are done"); //when this line runs, the for loop is complete.
}
}, 10); //10 means process 10 heavytask(), then
//yield back to the browser, and have the
//browser call us right back.
repeater.step(); //this command kicks off the recursive closure.
};
Adapted from this source:
http://www.picnet.com.au/blogs/Guido/post/2010/03/04/How-to-prevent-Stop-running-this-script-message-in-browsers
In my case, while playing video, I needed to call a function everytime currentTime of video updates. So I used timeupdate event of video and I came to know that it was fired at least 4 times a second (depends on the browser you use, see this). So I changed it to call a function every second like this:
var currentIntTime = 0;
var someFunction = function() {
currentIntTime++;
// Do something here
}
vidEl.on('timeupdate', function(){
if(parseInt(vidEl.currentTime) > currentIntTime) {
someFunction();
}
});
This reduces calls to someFunc by at least 1/3 and it may help your browser to behave normally. It did for me !!!
I can't comment on the previous answers since I haven't tried them. However I know the following strategy works for me. It is a bit less elegant but gets the job done. It also doesn't require breaking code into chunks like some other approaches seem to do. In my case, that was not an option, because my code had recursive calls to the logic that was being looped; i.e., there was no practical way to just hop out of the loop, then be able to resume in some way by using global vars to preserve current state since those globals could be changed by references to them in a subsequent recursed call. So I needed a straight-forward way that would not offer a chance for the code to compromise the data state integrity.
Assuming the "stop script?" dialog is coming up during a for() loop executuion after a number of iterations (in my case, about 8-10), and messing with the registry is no option, here was the fix (for me, anyway):
var anarray = [];
var array_member = null;
var counter = 0; // Could also be initialized to the max desired value you want, if
// planning on counting downward.
function func_a()
{
// some code
// optionally, set 'counter' to some desired value.
...
anarray = { populate array with objects to be processed that would have been
processed by a for() }
// 'anarry' is going to be reduced in size iteratively. Therefore, if you need
// to maintain an orig. copy of it, create one, something like 'anarraycopy'.
// If you need only a shallow copy, use 'anarraycopy = anarray.slice(0);'
// A deep copy, depending on what kind of objects you have in the array, may be
// necessary. The strategy for a deep copy will vary and is not discussed here.
// If you need merely to record the array's orig. size, set a local or
// global var equal to 'anarray.length;', depending on your needs.
// - or -
// plan to use 'counter' as if it was 'i' in a for(), as in
// for(i=0; i < x; i++ {...}
...
// Using 50 for example only. Could be 100, etc. Good practice is to pick something
// other than 0 due to Javascript engine processing; a 0 value is all but useless
// since it takes time for Javascript to do anything. 50 seems to be good value to
// use. It could be though that what value to use does depend on how much time it
// takes the code in func_c() to execute, so some profiling and knowing what the
// most likely deployed user base is going to be using might help. At the same
// time, this may make no difference. Not entirely sure myself. Also,
// using "'func_b()'" instead of just "func_b()" is critical. I've found that the
// callback will not occur unless you have the function in single-quotes.
setTimeout('func_b()', 50);
// No more code after this. function func_a() is now done. It's important not to
// put any more code in after this point since setTimeout() does not act like
// Thread.sleep() in Java. Processing just continues, and that is the problem
// you're trying to get around.
} // func_a()
function func_b()
{
if( anarray.length == 0 )
{
// possibly do something here, relevant to your purposes
return;
}
// -or-
if( counter == x ) // 'x' is some value you want to go to. It'll likely either
// be 0 (when counting down) or the max desired value you
// have for x if counting upward.
{
// possibly do something here, relevant to your purposes
return;
}
array_member = anarray[0];
anarray.splice(0,1); // Reduces 'anarray' by one member, the one at anarray[0].
// The one that was at anarray[1] is now at
// anarray[0] so will be used at the next iteration of func_b().
func_c();
setTimeout('func_b()', 50);
} // func_b()
function func_c()
{
counter++; // If not using 'anarray'. Possibly you would use
// 'counter--' if you set 'counter' to the highest value
// desired and are working your way backwards.
// Here is where you have the code that would have been executed
// in the for() loop. Breaking out of it or doing a 'continue'
// equivalent can be done with using 'return;' or canceling
// processing entirely can be done by setting a global var
// to indicate the process is cancelled, then doing a 'return;', as in
// 'bCancelOut = true; return;'. Then in func_b() you would be evaluating
// bCancelOut at the top to see if it was true. If so, you'd just exit from
// func_b() with a 'return;'
} // func_c()

Categories