OK, so this afternoon I got bitten by an issue that has bitten a gazillion web developers (and will probably continue to bite more in the future). I’m talking about the syntax for the callback function that’s used for jQuery.each()
versus that for JavaScript’s Array.prototype.forEach()
. They are, dear reader, not the same.
Let’s quickly show the difference. The callback for the jQuery.each()
function should have calling syntax that looks like this:
function (index, element)
whereas the one for JavaScript’s Array.prototype.forEach()
function looks like this:
function (item, index, array)
Pretty simple: the first callback will get called with the index first and then the element (the this
value is the element as well); whereas the second requires the item first and, if you really care about it, the index second (and the this
value is `undefined`). In general, I’ve found that I don’t really care about the index at all: I’m just going to process each item in the array (or jQuery object) in some way. The index? Frankly, my dear, I don’t give a damn.
So today I inadvertently wrote an Array-style callback function for jQuery.each()
and that managed to cause some wacky error in some other minified JS file, so I then spent some (wasted some?) time trying to work out what the heck was going on.
As Frankie said, no more. I shall now write a jQuery.forEach()
function that calls each()
internally and then use this from now on.
My first step was to write the forEach()
function preamble. Basically, for the syntax, I used the same as the Array forEach()
:
var forEach = function (callback, thisArg) {
"use strict";
// magic code
}
The thisArg
parameter is optional. If set, it’ll be used as the this
argument when calling the callback. At the moment, I’m not particularly worried about where this ‘template’ is going to be declared, hence the bare var
bit. In a moment, I’ll be extending jQuery with this function.
What I want to do is to call the standard each()
function. I’ll be setting things up so that this new function will be called on a jQuery object, so this
will be what each()
wants. each()
will be calling a callback function that’s as shown above:
var forEach = function (callback, thisArg) {
"use strict";
var self = this;
if (thisArg) {
self.each(function (i, item) {
// magic code
});
}
else {
self.each(function (i, item) {
// magic code
});
}
};
First, I’m going to save the value of this
for later use. Then we’ll be doing something different if thisArg
is defined or not, but either way we’ll be calling each()
with a standard looking callback for jQuery. Now for the fun stuff: within those callbacks, we have to call the passed-in callback.
var forEach = function (callback, thisArg) {
"use strict";
var self = this;
if (thisArg) {
self.each(function (i, item) {
return callback.call(thisArg, item, i, self);
});
}
else {
self.each(function (i, item) {
return callback.call(item, item, i, self);
});
}
};
I’m using the JavaScript Function.prototype.call
function to call the original callback. The first parameter to call
is the this
value to use (either thisArg
or the current item), and then the remainder of the arguments are those that the callback is expecting. Notice I pass the jQuery object all this was called on as the final argument. Notice also that I return the result of calling the callback: in jQuery if false
is returned the loop is immediately terminated.
Let’s tidy that up a little bit with a ternary operator (you can skip this step if you want: I go back and forth on whether ternary operators are the work of the devil or fine in these simple cases).
var forEach = function (callback, thisArg) {
"use strict";
var self = this;
self.each(function (i, item) {
return callback.call(!thisArg ? item : thisArg, item, i, self);
});
};
Works for me. although I am wincing a little bit at the ternary stuff. Now to wrap it all in something that will extend jQuery:
(function ($) {
"use strict";
$.fn.extend({
forEach: function (callback, thisArg) {
var self = this;
self.each(function (i, item) {
return callback.call(!thisArg ? item : thisArg, item, i, self);
});
}
});
}(jQuery));
I’ve declared an IIFE (Immediately Invoked Function Expression) that gets passed the jQuery object (so this has to be called after jQuery has been loaded in your HTML). All the IIFE does is to extend the jQuery prototype (jQuery.fn
) with an object with one method, our forEach()
function.
And that’s it. After this is run, jQuery now has a “standard-looking-and-behaving” forEach()
function, and I no longer have to worry about typing each
or forEach
(it’s the latter all the time) and what the callback looks like.
No Responses
Feel free to add a comment...
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response