56

People here often suggest to cache the jQuery object created from a DOM element, like with this code:

$('#container input').each(function() {
    $(this).addClass('fooClass');
    $(this).attr('data-bar', "bar");
    $(this).css('background-color', 'red');
});
  • Does caching the jQuery object really improve the performance of our code?
  • What happens "behind the scenes" when you pass a DOM element to the jQuery constructor?
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
gdoron
  • 147,333
  • 58
  • 291
  • 367
  • You should always cache, but in this specific example, you don't even need to do it. Just take advantage of jQuery chaining: `$(this).addClass('fooClass').attr('data-bar', "bar").css('background-color', 'red');` – Jose Rui Santos Jun 22 '15 at 11:34

4 Answers4

54

In the jQuery tag info this warning appears:

The jQuery function $() is expensive. Calling it repeatedly is extremely inefficient.

Well... that is true only for string selectors, which get parsed with regex to find out what they are:

quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/

Then if the string is a selector (other than id), jQuery traverses the DOM to find a match with its expensive find function:

} else if ( !context || context.jquery ) {
    return ( context || rootjQuery ).find( selector );
}

So yes it's expensive, but that is only true for selectors!

If we pass a DOMElement, the only action jQuery does is saving the DOMElement parameter as the context of the newly created jQuery object and setting the length of the context to 1:

// Handle $(DOMElement)
if ( selector.nodeType ) {
    this.context = this[0] = selector; // Selector here is a DOMElement
    this.length = 1;
    return this;
}

I did some tests with jsPerf, and I found that indeed caching the jQuery object has only a little effect:

Bar chart, described below

In Chrome it's only 7% slower. (In IE it's a little bit more significant: 12%.)

Community
  • 1
  • 1
gdoron
  • 147,333
  • 58
  • 291
  • 367
14

To answer your second question, look at the source:

// Handle $(DOMElement)
if ( selector.nodeType ) {
    this.context = this[0] = selector;
    this.length = 1;
    return this;
}
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    Here's a nice little app to view the source: http://james.padolsey.com/jquery/#v=git&fn=jQuery.fn.init – Joe May 03 '12 at 14:06
  • 3
    Now a string selector will obviously have a much different graph. – Joe May 03 '12 at 14:08
  • Thanks SLaks for pointing me to the jQuery source. I don't know why I didn't do that myself in the first place. – gdoron May 04 '12 at 01:37
10

With regard to the performance difference, if you're looking for a direct comparison between the two, it's helpful to remove any extra code that could skew the result, like DOM selection and other methods that aren't directly related.

http://jsperf.com/this-cost/2

enter image description here

In a more real world setting, the relative difference is minor as your test showed

Another thing to keep in mind is that every time you create a jQuery object, memory needs to be allocated for it, which adds to the work that the garbage collector needs to do.

So I think the reason people suggest caching is from somewhat of a principled standpoint. Extra work is being done that, while it usually won't have a noticeable impact, does ultimately require some overhead that can easily be avoided.

Undefined
  • 11,234
  • 5
  • 37
  • 62
cliffs of insanity
  • 3,683
  • 1
  • 16
  • 18
8

One thing that all of the runtime performance tests here miss is another major consideration:

Network bandwidth.

Caching $(this) into a local variable will generally decrease the size of your script, especially when minified (because this cannot be reduced from four characters).

Consider:

function hello(text) {
    $(this).attr();
    $(this).css();
    $(this).data();
    $(this).click();
    $(this).mouseover();
    $(this).mouseleave();
    $(this).html(text);
}
hello('Hello world');

Closure compiler's minified output is

function hello(a){$(this).attr();$(this).css();$(this).data();$(this).click();$(this).mouseover();$(this).mouseleave();$(this).html(a)}hello("Hello world");

This saves 39 bytes (20%). Now consider:

function hello(name) {
    var $this = $(this);
    $this.attr();
    $this.css();
    $this.data();
    $this.click();
    $this.mouseover();
    $this.mouseleave();
    $this.html(name);
}
hello('Hello world');

The minified output is

function hello(b){var a=$(this);a.attr();a.css();a.data();a.click();a.mouseover();a.mouseleave();a.html(b)}hello("Hello world");

This saves 74 bytes (37%), nearly doubling our byte savings. Obviously, real world savings in large scripts will be lower, but you still stand to gain significant reductions in the size of your script by caching.

Really, there's only an upside to caching $(this). You get miniscule but measurable runtime performance gains. More importantly, you can reduce the number of bytes that travel over the wire, and that directly translates to more dollars because faster page loads equal more sales.

When you look at it that way, you could actually say there's a quantifiable dollar cost to repeating $(this) and not caching it.

gdoron
  • 147,333
  • 58
  • 291
  • 367
josh3736
  • 139,160
  • 33
  • 216
  • 263
  • +1, Though it's more an answer to why you should cache `this` not `$(this)` because you can get the same result with `this.value; this.tagName; this.className; this.nodeType; this....` – gdoron May 04 '12 at 02:40
  • @gdoron, there's a big difference between using raw DOM and jQuery methods; they're not always interchangeable. (`addClass`, `data`, animation...) That aside, there's still a 3-byte-per-call difference between `var a = $(this); a...; a...;` and `var a = this; $(a)...; $(a)...;` – josh3736 May 04 '12 at 03:11
  • If you're gzipping your files, you'll often find that the file size is a little larger as a result of such caching. In your example, it's only a few bytes difference, 111 vs 115 bytes, but it underscores the point. I have no idea why this is, but I've often found this to be the case. – cliffs of insanity May 04 '12 at 14:55
  • @user1370958, the gzipped files are still smaller, there's just less savings. In the two examples above, savings from just minification are 20% and 37%; minified + gzipped savings are 7% and 12%. While gzipped content *can* be larger than the original content, this generally only happens for very small files (< 50 bytes). – josh3736 May 04 '12 at 16:10
  • 3
    Yes, I just meant that if you're gzipping, then caching certain things like `this` can result in a larger file as compared to having gzipped the version of the code with the non-cached `this`. They'll certainly both result in a smaller file than the original. It's not the percentage saved that's important, since the starting points are different, but rather the final file byte size is what needs to be measured. – cliffs of insanity May 04 '12 at 17:30