7/7/2012 - 1:04 PM

Avoid jQuery When Possible

Avoid jQuery When Possible

Avoid jQuery When Possible

jQuery does good jobs when you're dealing with browser compatibility. But we're living in an age that fewer and fewer people use old-school browsers such as IE <= 7. With the growing of DOM APIs in modern browsers (including IE 8), most functions that jQuery provides are built-in natively.

When targeting only modern browsers, it is better to avoid using jQuery's backward-compatible features. Instead, use the native DOM API, which will make your web page run much faster than you might think (native C / C++ implementaion v.s. JavaScript).

If you're making a web page for iOS (e.g. UIWebView), you should use native DOM APIs because mobile Safari is not that old-school web browser; it supports lots of native DOM APIs.

If you're making a Chrome Extension, you should always use native APIs, not only because Chrome has almost the latest DOM APIs available, but this can also avoid performance issue and unnecessary memory occupation (each jQuery-driven extension needs a separate memory space for its jQuery's compiled machine code; they're not shared across extensions). One exception is the app-like extension, which works just like another tab, and no need to care about memory occupation.


As long as you know the actual ID of the target element, why don't you use getElementById?

// instead of $("#header");

This works for IE >= 5.5

$(selector) & .find()querySelectorAll()

Use querySelectorAll() instead:

document.querySelectorAll("#header ul li");
// instead of $("#header ul li")

// or when you're finding in the context of #header

var header = document.getElementById("header");
header.querySelectorAll("ul li");
// instead of $("#header").find("ul li");

This works for IE >= 8.

$("<a class='blah' href='…'>hi</a>")createElement()

jQuery(html) is useful when you're doing DOM tree manipulation with that html snippet. But if you only have to create a new element, use the native DOM API createElement instead:

var element = document.createElement("a");
element.href = "";
element.className = "blah";
element.innerText = "hi";

The element.className lets you access the class attribute of that element. More about this in the section below.

class manipulation: use classList & className

jQuery provides some class manipulation functions such as addClass(), removeClass(), toggleClass() as well as hasClass(). With the new DOM API classList, we can avoid these functions.

Unfortunately IE <= 9 does not support classList. In this case you can use polyfills like classList.js or this polyfill for IE 9, or implement it manually by tokenizing the className string into an array like how jQuery does, or by working with space-wrapped strings like how MooTools does.

classList Usage

var element = document.getElementById("header");

// add class
// instead of $(element).addClass("holiday-special")

// for IE <= 9, we can do this: // mind the space!
element.className += " holiday-special";

// remove class
// instead of $(element).removeClass("holiday-special")

// toggle a class
// instead of $(element).toggleClass("hide")

// test if the element has some class
// instead of $(element).hasClass("hide")

.css()style attribute & style.cssText

jQuery's .css() is useful for style manipulation (merging one set into another set). But if you only have to assign one attribute or override the whole style rule, don't use it.

Especially, if you care about performance, or doing lots of style manipulation, you should avoid .css() as possible as you can, because .css is really slow and will be your performance bottleneck. (Experiment; another experiment by me.)


Assigning One Attribute

e.g. Width = "320px";
// instead of $(element).css("width", "320px");

Overriding the Whole Style Rule

Write the style directly with Note that this is just like writing inline style directly, so mind that semicolon ;.

e.g. Width = 320px, Height = 640px = "width: 320px; height: 640px;"; // Mind the semicolon
// instead of $(element).css({"width": "320px", "height": "640px"});

$.each() → use for statement or .forEach (ECMAScript 5)

jQuery's $.each() is convenient when dealing with both Array and Object types, but we should already know the actual data type and process it properly.

For Arrays, there is a native for loop; for Objects, there is a native iterator.

Isolated Variable Scope

However for statements does not provide isolated variable scope (i.e. process each item with a callback function), which $.each() does provide. Fortunately in ECMAScript 5 there is an array.forEach function that provides an isolated variable scope with a callback function (polyfill or ES5-Shim), just like $.each. But so far there is no object.forEach available. In this case you may need $.each.

.data() → use HTML5 dataset (for string)

jQuery's .data() can store various types of data within a given element. However it implements its own storage, which causes a lot of overhead if you're only storing Strings with it.

Modern browsers has native HTML5 DataSet implementation. With element.dataset and its corresponding APIs, you can access the data bounded by data-* attributes. Also see HTML5 Doctor's Guide on HTML5 data-* Attributes.

The only limitation of HTML5 DataSet is that it can only store String type. .data() in jQuery can store any type of data, not only a nested Object or Array, but also a Function. If you do need to store types other than String, it is better to use .data(). It is also a better way to store a Number, because if you use DataSet you'll need to parseInt every time before you process the data.

Say that we have the following HTML:

<div id="long-cat" data-cat-length="30km">Nyan</div>

Access the cat-length data with dataset:

var element = document.getElementById("long-cat");

// read the existing data:
element.dataset.catLength; // => "30km"

// write the existing data:
element.dataset.catLength = "42km";
element.dataset.catLength; // => "42km"

// same way to create a new data dynamically:
element.dataset.catWeight = "100t";
element.dataset.catWeight; // => "100t"

Note that the name of data will be converted to camelCase, see the spec for more details.

Sometimes .data() is Useful

The following scenarios shows when .data() is more useful than DataSet:

var element1 = document.createElement("div"),
    element2 = document.createElement("div");

/* Storing a Number */

element1.dataset.length = 42000;
element1.dataset.length; // => "42000", a String, not a Number

$(element2).data("length", 42000);
$(element2).data("length"); // => 42000, a Number, as expected

/* Storing an Object */

element1.dataset.profile = { name: "Nyan", age: 3};
element1.dataset.profile; // => [object Object], not what we want.

$(element2).data("profile", { name: "Nyan", age: 3});
$(element2).data("profile"); // => { name: "Nyan", age: 3}, an Object, as expected

/* Storing an Array */

element1.dataset.primes = [2, 3, 5, 7, 11, 13];
element1.dataset.primes; // => "2,3,5,7,11,13", a String, not an Array

$(element2).data("primes", [2, 3, 5, 7, 11, 13]);
$(element2).data("primes"); // => [2, 3, 5, 7, 11, 13], an Array, as expected

/* Storing a Function */

var scaling = function(n) {
  return n * 3;

element1.dataset.scaling = scaling;
element1.dataset.scaling; // => "function(n) {...}", a String representing the function literally, not a Function

$(element2).data("scaling", scaling);
$(element2).data("scaling"); // => function(n) {...}, a Function, as expected

Support for Old Browsers

If you need to support old browsers, take a look at this polyfill, or use getAttribute and setAttribute. For browsers without getAttribute and setAttribute support, e.g. IE 7, you need createAttribute, getAttributeNode and setAttributeNode, although they're deprecated in DOM 4 spec.

Example: Use getAttribute and setAttribute

// use the same HTML structure above

var element = document.getElementById("long-cat");

// read the existing data:
element.getAttribute("data-cat-length"); // => "30km"

// write the existing data:
element.setAttribute("data-cat-length", "42km");
element.getAttribute("data-cat-length"); // => "42km"

// write a new data dynamically:
element.setAttribute("data-cat-weight", "100t");
element.getAttributeNode("data-cat-weight"); // => "100t"

Example: Use createAttribute, getAttributeNode and setAttributeNode (deprecated in DOM 4)

// use the same HTML structure above

var element = document.getElementById("long-cat");

// read the existing data:
element.getAttributeNode("data-cat-length").nodeValue; // => "30km"

// write the existing data:
element.getAttributeNode("data-cat-length").nodeValue = "42km"
element.getAttributeNode("data-cat-length").nodeValue; // => "42km"

// create a new data dynamically:
var attribute = document.createAttribute("data-cat-weight");
attribute.nodeValue = "100t";

element.getAttributeNode("data-cat-weight").nodeValue; // => "100t"

How to Tell the Performance Improvement?

Desktop WebKit browsers like Chrome and Safari, as well as Internet Explorer 9+, have built-in JavaScript CPU Profilers. Use them to trace function running time and determine the bottleneck of your javascript code:


  1. Gecko DOM Reference - MDN

p.s. This is made public from my private Gist.