Pitfalls of jQuery.each

Even a very simple function like the convenient $.each can hide some non obvious traps.

Not the usual magic

Let’s say you want to iterate over the elements in your page having the class indent. You might write

$.each('.indent', function(){

Maybe you immediately saw the problem, maybe not. The reason it doesn’t work is that this won’t iterate over $('.indent') but over the '.indent' string (this being every character in turn).

In my opinion, what is the most interesting here is why many developers accustomed to jQuery fall into this trap, even while the documentation never pretends $.each should iterate over the DOM elements. The reason is that we’re used to jQuery functions magically guessing the nature of the arguments they receive. We’re used to jQuery finding on his own how to deal with the following ones :

$(function(){...}) // a function, obviously
$({}) // some object
$(document) // a DOM node
$('.indent') // a selector
$('<div>Hi!</div>') // a string too, but this one looks like HTML

Here, the « problem » is that the usual magic isn’t applied. $.each mainly tries to guess if the argument is array-like (for example a string) or object-like (then it must iterate using for...each). jQuery argument interpretation makes it intuitive and concise but sometimes you’d better check how the arguments are interpreted.

Callback arguments order

Another pitfalls is that it’s hard to remember the order of the callback arguments, because it’s just not the same as the standard ECMAScript foreach :

[1, 2, 3].foreach(function(value, index) { ... });
$.each([1, 2, 3], function(index, value) { ... });

Confusing, isn’t it ? Why does jQuery give as first argument the least interesting one ?

In fact there is a good rationale : Like many jQuery functions, $.each conveniently provides the value as context of the function call.

Just like you’re accustomed to using this in

$('.indent').each(function(){ $(this).text( ....

you can get the value with this :

var product = 1;
$.each([1, 2, 3], function(){ product *= this });

Now it seems you don’t need to remember the callback arguments order if you just want the value ? Marvelous ?

In fact, it’s useful but it leads to the third pitfall :

this isn’t really the value

this is often the value but not always. Look at this code :

​[1, 2, 3].forEach(function(v) {
    if (v===2) console.log('found!');
});

Output in the console :

found!

Now with jQuery :

$.each([1, 2, 3], function() {
    if (this===2) console.log('found!');
});

What’s the output ?

Nothing.

I explain it in more details in Stack Overflow but basically it’s because the context of a function can’t be a primitive value so it has to be embedded into a boxing object, here an instance of Number and new Number(2)!===2.

As new Number(2)==2, he following code would have found the element :

$.each([1, 2, 3], function() {
    if (this==2) console.log('found!');
});

Conclusion

$.each is convenient and useful. It lets you iterate on arrays and objects in a concise and cross-browser way. In my opinion it’s globally well designed, as there were compromises to make. You should use it when the need arises. But as I saw answering on Stack Overflow it can be sometimes confusing and you’d better think about what it does and why.

Les commentaires sont fermés.