I would strongly suggest to any serious front-end developer to (really) learn JavaScript and understand the basics of DOM. Of course, some may argue that many JavaScript-specific hacks and tricks do not have much impact on performance as perceived by the final user, and I completely agree. That being said, in this article I’ll share some techniques that you can implement right now on your current code base to make it faster, and from now on, you should have them in mind whenever you write JavaScript for future projects.
Use selectors wisely
Let’s say you have a div with certain id #profile-container, and you need to get one or more input elements with the class “myClass” inside the div. You may quickly come up with a jQuery selector like this:
$('#profile-container input.myClass')
This get the work done, but it may not be the best way to do it. In fact$(‘#profile-container’).find(‘input.myClass’) would be faster. The reason for that? How JQuery selector engine works. Basically, a $(‘#profile-container’) selection would be pretty fast and straight forward to make, and whenever you chain the function find(), it will be restricted to a very limited search space, improving the performance in general. Take a look at JQuery source.
In this article written by Rob Tarr you’ll find some experiments and tests that validate this point. Even more, the author found out that is even better to chain find() calls, for instance, $(‘.container’).find(‘.main’).find(‘ul.list-1′).find(‘li’)
Another wise way to improve JQuery selectors performance is to explicitly declare the element type we’re looking for, in case we know it before hand. This, $(‘ul.todo’) would always be better than $(‘.todo’) on reasons of element specificity.
Cache jQuery selector results
This one is very well known, because of the classic reasons of performance, “don’t repeat yourself” and best practices. In Greg Franko’s excellent slides “jQuery best practices”, we find an interesting example:
// Set's an element's title attribute using it's current text $(".container input#elem").attr("title", $(".container input#elem").text()); // Set's an element's text color to red $(".container input#elem").css("color", "red"); // Makes the element fade out $(".container input#elem").fadeOut();
The problem is that every time you call $(“.container input#elem”), jQuery needs to search for your selector, which potentially means traversing all the DOM. In this case, the look up for “.container input#elem” would be performed four times! You can very easily rewrite it as:
// Stores the live DOM element inside of a variable var elem = $("#elem"); // Set's an element's title attribute using it's current text elem.attr("title", elem.text()); // Set's an element's text color to red elem.css("color", "red"); // Makes the element fade out elem.fadeOut();
Check out we’re also using selectors wisely, as we’re looking for an id, there’s no need to loop up after a class or element in particular. #elem is easier and way faster.
Cache .length property
In JavaScript, every time you call the property .length of an Array it will be calculated for every time you try to access it. So, if you have something like
for (var i = 0; i < myArray.length; i++){...}
Should the size of myArray happens to be 10,000, the value of myArray.length will be computed 10,000 times, once for every loop turn. This would be much better:
var arrayLength = myArray.length; for (var i = 0; i < arrayLength; i++){...}
Henceforth the value of arrayLength will be calculated only once.
Does this matter? In modern browser the difference is absurdly minimal, as their JavaScript engine already do this sort of optimization. Should you forget completely about it? Well, according to Thomas Lahn from comp.lang.javascript newsgroup:
One should never rely on what one cannot know. You cannot know the runtime environments code once written will be exposed to. There is no good reason to assume the value of the “length” property should be automatically cached for a loop in the first place as it could change by statements in the loop.
One can know instead that avoiding to access a property repeatedly whose value does not change, in favor of a local variable that holds this value, reduces code complexity. That has a chance – and it has been showed – to increase runtime efficiency, IOW to be faster. So it is wise to do that.
Minimize DOM operations
Writing into the DOM is a heavy operation. Remember: the DOM is slowand if you’re not aware of that, you’ll be stuck in performance issues sooner than later. This represent a classic example of a very heavy operation for the browser:
var toDoList = $("#todoList"); myTasks.forEach(function(task){ toDoList.append("<li id=" + task.index + ">" + task.name + "</li>"); });
In that example, we’re reading and writing the DOM in every forEach cycle. You can avoid mass element injection by storing your nodes into a variable and then inject them in the DOM after the loop is done, hence appending only once:
var toDoList = $("#todoList"); dynamicItems = ""; myTasks.forEach(function(task){ dynamicItems += "<li id=" + index + ">" + value + "</li>"; }); toDoList.append(dynamicItems);
Avoid repeated object creation
If you create an object inside a function, always keep in mind that object will be created every time the function is invoked. This might not be want you really want, specially when your object is static and it’s not expected to change over time. Consider this example by David Walsh:
function cleanText(dirty) { // Get rid of SCRIPT tags clean = dirty.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, ""); // Do some more cleaning, maybe whitespace, etc. return clean; }
The literal notation // is a shorthand for new RegExp, so every time you call something like /ab+c/ you’re actually creating a new RegExp object:
//both expressions are equivalent var re1 = /ab+c/; var re2 = new RegExp("ab+c");
In our example function, we don’t really need to create a new regular expression object everytime, you we can actually create it outside the function and access to it through a closure scope:
var scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi; function cleanText(dirty) { // Get rid of SCRIPT tags clean = dirty.replace(scriptRegex, ""); // Do some more cleaning, maybe whitespace, etc. return clean; }
Delegate event listeners
Assigning event listeners to individual elements may take up a lot of memory and is expensive if you create lots of new elements dynamically to which new event handlers need to be bound.
document.querySelector('#todoList li').addEventListener("click", function() { alert("Clicked on a task"); });
If your #todoList has 10,000 li elements that would mean 10,000 event handlers. Event delegation replaces the need for adding event listeners to individual items by instead placing one event listener on a given parent. The example above can be rewritten as:
document.querySelector('#todoList').addEventListener('click', function(e) { if (e.target && e.target.tagName == 'LI') { alert("Clicked on a task"); } });
Event delegation is even easier with jQuery:
$("#todoList").on("click", 'li', function() { alert("Clicked on a task"); });
Stop using jQuery when you only need selectors
We know, JQuery is the best tool to traverse the DOM, but if your project is only using jQuery for this solely purpose, you have to wonder if it’s really worthy to load an external library when we have document.querySelectorAll, which performs basically the same selector fetching operations as JQuery. Yep that’s right, you can use a native browser implementation to do all your daily selector tasks, such as document.querySelectorAll(‘.content ul li’). Is it long and ugly you say? Well, take a look at the example provided by Burke Holland in his article 5 Things You Should Stop Doing With jQuery:
<div class="container"> <ul> <li id="pink">Pink</li> <li id="salmon">Salmon</li> <li id="blue">Blue</li> <li id="green">Green</li> <li id="red">Red</li> </ul> </div> <script> // create a global '$' variable window.$ = function(selector) { return document.querySelector(selector); }; (function() { // select item1 by id and change it's background color to salmon var item = $("#salmon").style.backgroundColor="salmon"; console.log(item); }()); </script>
In this way you can still use your loved $(‘mySelector’) syntax.
How faster is querySelectorAll compared to jQuery selectors? More than five times depending on your browser. Although JQuery would automatically delegate to document.querySelectorAll if present, you would still ponder if you really need to load those ~90kB.
Do you want to keep learning? I would recommend learning about profiling and managing data structures efficiently in JavaScript, I might write about it in the future, but in the meantime take a look at this article.