Game on: jQuery each() vs. Array.prototype.forEach()

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.

Abbaye de Jumièges: the nave

Album cover for Solid PleasureNow playing:
Yello - Reverse Lion
(from Solid Pleasure)


Loading similar posts...   Loading links to posts on similar topics...

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.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response