I’ve always questioned the sanity of the people who do something similar to the former example in d3.js; this is so much easier: http://pygm.us/IhBQwUAI.
In most other languages, the second form makes more sense. But jQuery is doing things under the surface that make the syntax change inconsequential.
In my performance tests (Win 8, Chrome 25), the chained method actually performed faster than the object method. So while the idea of passing multiple arguments to a single function call (as opposed to making multiple function calls) might make sense for a general programming best practice, when applied to jQuery it fails.
Nice point about the object loop. I would image though that the object would have relatively few properties whereas the number of DOM elements in the jQuery collection could vary dramatically based on the UI (think a long list of list items).
That doesn't matter, though. Let's take the example above and say there's 5000 .widget items on the page. There's two properties that need to be set. Assuming jQuery works as you expect, in the first case we iterate over 5000 elements setting one attribute, then do it again, for 10000 total operations. In the second case, it iterates over 5000 elements once, but sets two attributes, which is again 10000 total operations.
More searching finds access here: https://github.com/jquery/jquery/blob/master/src/core.js#L71... . Note line 719: If the key (which is the first argument to css) is an object, it just iterates through the object and recursively calls itself for each key-value pair in the object.
Thus, passing an object to css is equivalent to calling css multiple times, ignoring small costs of re-building the callback function that css passes to access (and I'm not even sure that cost exists, I'd actually expect any decent interpreter to optimize such that re-building it is of negligible performance cost).
The jQuery source code is a rather fun read, I highly recommend stepping through it from time to time. :)
My results (Chrome, Mac) are showing they have the same performance. On smaller HTML pages, multiple calls to css() actually outperformed the single call significantly (~30%).
You are correct in that jQuery doesn't re-query the DOM for elements on each call, but what it does do is re-loop through it's internal cached collection to apply these CSS rules. The perf hit isn't noticeable when jQuery's selection (internal collection) is small. Imagine a webpage with lots of elements you want to select and then want to interact with... that is what I am trying to bring attention to.
Is it re-looping? Doesn't the .css setter return a jQuery object that is used by the next method in the chain? So it's actually only going through an implicit loop of $( ".widget" ) once?
It's looping twice, because the first time you call to .css it loops through each element to add the first style, and then it loops through again on the second .css to add the second style. You could roughly replicate it with:
var i, elements = document.getElementsByClassName('widget');
for (i = 0; i < elements.length; i++) {
elements[i].style.color = "green";
}
for (i = 0; i < elements.length; i++) {
elements[i].style.fontSize = "16px";
}
Two loops. The "improved" suggestion would be roughly equivalent to:
var i, elements = document.getElementsByClassName('widget');
for (i = 0; i < elements.length; i++) {
elements[i].style.color = "green";
elements[i].style.fontSize = "16px";
}
This is one loop, but it's still O(n). If there are 100 ".widget" elements on the page, the first code does 200 writes to the style property. The second code does 200 writes to the style property. The difference in jumps and compares isn't worth thinking about when style changes are so expensive, the difference is probably immeasurable.
The second version is better just because it's cleaner code, but that's it.
I'm not convinced that there's two loops -- aren't the .css() methods chained together, and would be executed right after each other for each step through the implied loop?
In
$(".widget").css("color", "green").css("font-size", "16px");
does the color of all the widgets change, and then the fontSize, or does the color/fontSize change together for each widget?
I suspect the latter...
Chained functions call independently and completely before returning to the next. The css function [0] operates on the "array" this returned by $ using an anonymous function [1] at a call site in the access function [1] [2]; access then returns the object formerly known as this [3]. Chain, rinse, and repeat to your heart's content.
There must obviously be two loops, jQuery can't perform magic. The first .css() call will iterate over the cached array of elements, apply the color attributes and then return a reference to the jQuery instance. The second call will loop once again and this time apply the font-size attribute.
There is no extra DOM lookups, but there will certainly be two loops.
There is another explanation than the one given for seeing examples like this in someone else's code:
// Indications of Confusion
$( ".widget" ).each( function() {
$( this ).css( "color", "green" );
});
I think this is at least as likely to be the remains of a block of code that was pared down and not (yet) re-implemented differently. The developer might have intended to return with replacement code and never did, perhaps this is an ongoing project on which you're a collaborator, perhaps the developer just forgot about it. It may still be bad code, but concluding wherever you find it that the reason is fundamental ignorance seems hasty.
It will always be a struggle for me to resist the bias against Other People's Code that causes me to underestimate the developer whenever I encounter something wrong. Maybe it's a minor point, but I think this bias exists in everyone and that this is an example of it.
http://jsperf.com/chained-css-declarations-jquery