I am populating a list with about 25,000 items, using code like this:
var html = "";
for(var i = 0; i < reallyLongArray.length; i++) {
html += "<li><a href='#'>Hi</a></li>";
}
$("#list ol").html(html);
Somewhat to my surprise, I used a profiler and found out that the bottleneck in my code was not the loop that iterated thousands of times, but setting the html of the list to the string. This usually takes about 5-10 seconds on my computer, which is an order of magnitude too slow.
Is there a way to do this that is significantly faster, i.e., at least 10 times faster?
Wrap the html in a single item. When jQuery builds elements from a string, it adds all top level items by iterating them. If you wrap the list items in a single element, it should go much faster because it only has to add 1 top level element to the dom.
var html = "<ul>";
// your loop...
var html += "</ul>";
// add list html to list container
Aside from directly using innerHTML:
$("#list ol").get(0).innerHTML = html;
...and trying the "stringbuffer" technique:
var html = [];
for(i = 0; i < reallyLongArray.length; i++) {
html.push("<li><a href='#'>Hi</a></li>");
}
$("#list ol").html(html.join(''));
...not really.
Using DOM methods to create it should work faster:
var list = ("#list ol");
for(i = 0; i < reallyLongArray.length; i++) {
$(document.createElement('li'))
.append($(document.createElement('a'))
.text('Hi')
.attr({href: 'foobar'})
)
.appendTo(list);
}
edit: Actually, using DocumentFragment should make this even faster:
var fragment = document.createDocumentFragment();
for(i = 0; i < reallyLongArray.length; i++) {
fragment.appendChild($(document.createElement('li'))
.append($(document.createElement('a'))
.text('Hi')
.attr({href: 'foobar'})
)
.get(0)
);
}
$('list ol').append(fragment);
You might also want to clear() the <ol> before adding the elements to it
another edit I've created a jsperf test at http://jsperf.com/insert-html-vs-dom-manipulation - both those versions are slower than setting the innerHTML (because jQuery is used to create the elements). Using dom maniuplation with native methods is much faster than setting the HTML, BUT the fastest way, by a large margin, is using DOM manipulation with DocumentFragment without jQuery, using native methods.
Array joins are faster than string manipulation.
var html[];
for(i = 0; i < reallyLongArray.length; i++) {
html.push(string);
}
$(selector).html(html.join(''));
This will speed it up by quite a bit. String concatenation can take a long time when used a lot because of what happens "under the hood".
$(document).ready(function(){
var html = new Array();
for(i = 0; i < reallyLongArray.length; i++) {
html[i] = "<li><a href='#'>Hi</a></li>";
}
document.getElementById('list').getElementsByTagName('ol')[0].innerHTML = html.join("");
});
One thing to just note here, is that when doing an iteration of 25,000, it will be difficult to reduce the time down to milliseconds if you are inserting a large number of records. This happens especially in IE as it parses each new element before inserting it. Building a "pager" and caching the items you are going to insert will significantly speed that up.
$.grep(ComboBoxData, function (e) {
if (e.Text.toLowerCase().indexOf(TextBox) != -1 || e.ID.toLowerCase().indexOf(TextBox) != -1) {
Show = true;
Result += "<li hdnTextID='" + hdTextID + "' onclick='Select(this," + TextBoxID + ")' onmouseover='triggerTheli(this)' HdntriggerID='" + e.HdntriggerID + "' postBack='" + e.TriggerPostBack + "' Event='" + e.EventName + "' HdnID='" + e.hdnID + "' SelectedValue='" + e.ID + "' style='background:" + Color + "'><a>" + e.Text + "<p> " + e.Description + " </p></a></li>";
if (Color == "#eff3ec")
Color = "#fff";
else
Color = "#eff3ec";
}
});
a good example of mine
Related
I've recently started learning about JavaScript objects, and how frameworks such as jQuery, and Modernizr work.
I have tried to create my own little "framework", to further learn how JavaScript objects work, and how to utilize them to their full potential.
It's all gone pretty smoothly so far, until I tried to set a global variable using the querySelectorAll() method (and a for loop), to grab multiple elements with the specified selector.
With this, I intended to add or remove a class from each of those elements with that specific selector. However, it only ever worked on the very last element of the bunch.
Here is my (relevant) JavaScript:
var aj = function(sr){
this.selector = sr || null; // set global selector variable
this.element = null;
}
aj.prototype.init = function(){
switch(this.selector[0]){
// first, second, third case e.t.c...
default:
var els = document.querySelectorAll(this.selector); // select all elements with specified selector (set above)
for(var i = 0; i < els.length; i++){
this.element = els[i];
}
}
};
aj.prototype.class = function(type, classes){
if(type === "add"){ // if the user wants to add a class
if((" " + this.element.className + " ").indexOf(" " + classes + " ") < 0){
this.element.className += " " + classes;
}
} else if(type === "remove") { // if the want to remove a class
var regex = new RegExp("(^| )" + classes + "($| )", "g");
this.element.className = this.element.className.replace(regex, " ");
}
};
Example:
<div class="example-class">Example</div>
<div class="example-class">Example 2</div> <!-- only this one will be altered !-->
<script>
$(".example-class").class("add", "classname");
</script>
Why would this be occurring? My for loop appears to be correct, so I am unsure what is wrong. Apologies if it appears pretty obvious, however, I'm still new to vanilla JavaScript.
All help (and suggestions) is appreciated,
Thanks.
for(var i = 0; i < els.length; i++){
this.element = els[i];
}
You have a loop. Each time it goes around the loop it assigns a value to this.element.
The first time it goes around the loop it assigns the value of els[0]. The second time it assigns the value of els[1].
Since you only have two elements that match, it reaches the end of the loop and stops.
At this point, this.element is still equal to els[1].
If you want to do something (like add membership of a class) to each item in els then you have to loop over els at the time you modify className.
When I need to add up about 2000 elements by jQuery function append(), browser gets freeze, and seems everything stuck for some time long.
Is this because of jQuery it'self issue or.. any logical issue on my coding side?
What I am doing is like following:
for (var i=0; data.length; i++) {
add_elem(data[i]);
}
function add_elem(data) {
$("#wrapper").append('<div class="row" id="elem_' + data['id'] + '>' + data['htmlv'] + '</div>');
uniform_elem($('#elem_' + data['id']));
}
uniform_elem() has some ui processing to make things look good.
Any good idea?
Instead of calling add_elem in the loop, store all of the html for the elements in a single variable as you loop through the data variable and then invoke add_elem by passing in the new variable.
var elements = "";
// Store all elements in the loop
for (var i=0; data.length; i++) {
elements+= '<div class="row" id="elem_' + data['id'] + '>' + data['htmlv'] + '</div>';
}
// Invoke add_elem once and only once using the new elements var
add_elem(elements);
function add_elem(data) {
// Append once to the DOM
$("#wrapper").append(data);
for (var j=0; data.length; j++) {
uniform_elem($('#elem_' + data['id']));
}
}
If you want to work with each object, you may do:
for (var i=0;i< data.length; i++) {
let u=i;
setTimeout(()=>{
add_elem(data[u]);
},0);
}
This pushes the calls onto the browsers qeue, wich is just executed when the main thread is free ( so no blocking)
Any way to get the ".indexOf()" string position of the start and end of a jQuery-selected element relative to the parent?
Example:
<ul><li>Coffee</li><li>Coffee</li><li>Coffee</li></ul>
After selecting the 2nd <li> item via jquery, the function would return 15 (or 29 with an argument switch).
I am thinking about a verbose javascript workaround but it gets nasty and unreliable very fast. Also it might not work if there are other tag types or text mixed in between. Bad. Not to take seriously.
//http://stackoverflow.com/a/14482123
function nthIndex(str, pat, n){
var L= str.length, i= -1;
while(n-- && i++<L){
i= str.indexOf(pat, i);
}
return i;
}
function strpos(jelem, tag) {
var nth = jelem.index();
var contents = jelem.parent().text();
var pos = nthIndex(contents, tag, nth+1);
if (tag.indexOf('/') == 1) {
return pos+4; //because ending of '</li>' is +4 (very bad)
} else {
return pos;
}
}
strpos(jelem, '<li>'); //or '</li>' to return the ending position
Like adeneo said, this seems like an XY problem, but you really need a parser and I doubt you feel like writing your own.
One possibility would be to use the native .outerHTML and sum the .length of all the .previousSibling nodes, and then the .length of current one if you need the end position.
However, you should know that this has nothing to do with the original HTML. These will be HTML strings rendered by the browser after analysing the current state of the DOM.
Here's a quick example that receives an element and returns the start (or optionally the end) position:
function getPos(origElem, doEnding) {
var sum = 0;
var elem = origElem;
while ((elem = elem.previousSibling)) {
sum += elem.outerHTML.length;
}
if (doEnding) {
sum += origElem.outerHTML.length-1;
}
return sum;
}
var li = document.querySelector("li:nth-child(2)");
var p = document.querySelector("pre");
p.textContent = "Position is: " + getPos(li);
p.textContent += "\nEnd position is: " + getPos(li, true);
<ul><li>Coffee</li><li>Coffee</li><li>Coffee</li></ul>
<pre></pre>
I am using the below code in my program but it seems that these few line of code is taking too much time to execute. For 100 iteration it is consuming 1 mins approx. for 200+ iteration my broser is showing a warning message that script is taking too much time. As per the scenario 500+ ids can be pushed into the array.
for (var i = 0; i < arrid.length; i++)
{
$("#" + outerDiv + "> div[id=" + arr[i] + "]").attr("class", "selected");
}
arrid is an array of div ids. Outerdiv is the the parent div of all these div ids present in arrid. arr ids cannot be accessed directly, it has to be referenced using the parent div i.e. outerDiv.
One quick thing you could do is cache your selector so jQuery does not have to query the dom 500+ times.
var $div = $("#" + outerDiv);
for (var i = 0; i < arrid.length; i++)
{
$div.children("div[id=" + arr[i] + "]").attr("class", "selected");
}
On second thought, since you have a list of id's, you shouldn't need any of that as the id should be unique per the dom.
for (var i = 0; i < arrid.length; i++)
{
$("#" + arr[i]).attr("class", "selected");;
}
If outerDiv is an element, you can write
for (var i = 0; i < arrid.length; i++)
{
$("#" +arr[i], outerDiv).attr("class", "selected");
}
But assuming the id's are unique, you shouldn't need to reference the outer div at all. It might even be faster not to.
Also, if this is all you're doing and you're concerned about performance, why not just use plain ol' javascript?
for (var i = 0; i < arrid.length; i++)
{
document.getElementById(arr[i]).className = "selected";
}
Using jQuery function is expensive - as is manipulating the DOM.
You can reduce to one call to jQuery like this:
var divSelector = [];
for (var i = 0; i < arrid.length; i++)
{ // add selector to array
divSelector.push( "#" + outerDiv + "> div[id=" + arr[i] + "]" );
}
// execute jquery once with many selectors
$(divSelector.join(',')).attr("class", "selected");
This code is untested, but should work in principal.
Don't call jQuery selection function too many times
Instead select your elements once and do the rest in a different way. For this thing to work it would be better to convert your arr array of IDs into an accosiative array that can make searching much much faster.
// convert arr = ["idOne", "idTwo", ...]
// into an associative array/object
// a = { idOne: true, idTwo: true, ... }
var a = {};
$.each(arr, function(index, el){
a[el] = true;
})
// do the rest
$("#" + outerDiv " > div[id]").each(function(){
if (a[this.id] === true)
{
$(this).addClass("selected");
}
})
The most inner call can be as well replaced with:
this.className = "selected"
when you can be sure no other classes will be added to the element.
But when you want to set selected on all child div elements (if your IDs cover all elements) then a simple:
$("#" + outerDiv " > div[id]").addClass("selected");
would do the trick just fine.
Store the instance into a variable($div) and use another variable to store the max length the loop for will do.
var $div = $("#" + outerDiv);
for (var i = 0, max = arrid.length; i < max; i++)
{
$div.children("div[id=" + arr[i] + "]").attr("class", "selected");
}
How about this as you have the id's of all the elements you are trying to change the class on:
for (var i = 0; i < arrid.length; i++)
{
$("#" + arr[i]).addClass("selected");
}
I know using the selector div[id=val] performs very slow is certain browser based on a speed test I ran, worth a look as it is pretty interesting:
http://mootools.net/slickspeed/
I think you can directly push jQuery object to the array, then you can just arr[i].attr('class', 'selected')
this following piece of code works, but it sort of makes the browser quirk just a bit. Nothing major. I'm wondering if there would be a way to make this more efficient? Can I use caching or somehow populate one select and then just copy it to the other 5. (there are 6 drop downs with a class of 'mask' on the page.)
Any help would be greatly appreciated!
$('.mask').each(function () {
$(this).append($('<option/>').val("").text(""));
for (var i = 1; i < 256; i++) {
$(this).append($('<option/>').val(i).text(i));
}
});
});
You can create the nodes once then clone them, like this:
var temp = $('<select/>');
$('<option/>').val("").text("").appendTo(temp);
for (var i = 1; i < 256; i++) {
$('<option/>').val(i).text(i).appendTo(temp);
}
temp.children().clone().appendTo('.mask');
Instead of doing a lot of individual appends to the DOM (which is very costly) this batches all the elements up in a document fragment then clones them, appending in batches (one batch per select).
It's much faster (around 3-10 times, as tested here) to build up the HTML yourself in a single string:
var html = "<option value=''></option>";
for (var i = 1; i < 256; i++) {
html += "<option value='" + i + "'>" + i + "</option>";
}
$(".mask").append(html);
See performance test comparing the current options in this thread: http://jsperf.com/appending-options-jquery