Iteratively speaking
In order to better illustrate their internal differences, I thought it would be interesting to create polyfills for some Array.prototype
methods. What follows is the code for polyfills, example usage, and a little discussion.
Here is a polyfill for Array.prototype.map()
.
Array.prototype.$map = function $map(callback) {
var newArray = [];
for (var index = 0; index < this.length; index++) {
var item = this[index];
// Always push return value of callback to the new array.
newArray.push(callback(item, index, this));
}
return newArray;
};
var result = [1, 2, 3, 4, 5].$map(function (item) {
return item + 1;
});
console.log('$map result', result); // [2, 3, 4, 5, 6]
What we'll end up seeing is that really the main difference between the methods discussed here, is just how the callback
argument is treated. So in the example above, our polyfill equivalent of Array.prorotype.map()
pushes the result of the callback to the newly returned array value. But below, in the polyfill for Array.prototype.filter()
, the original array's values are only pushed onto the array to be returned, if each execution of callback
evaluates as true
.
Array.prototype.$filter = function $filter(callback) {
var newArray = [];
for (var index = 0; index < this.length; index++) {
var item = this[index];
// Only push each item to the new array if the callback evaluates as true.
if (callback(item, index, this) === true) {
newArray.push(item);
}
}
return newArray;
};
var result = [1, 2, 3, 4, 5].$filter(function (item) {
return item % 2 === 0;
});
console.log('$filter result', result); // [2, 4]
So we've seen methods that directly use the return value of callback
as well as evaluating the boolean equivalancy of the return value. There is another method that is only interested in the side-effects of the evaluation of callback
. This method is Array.prototype.forEach()
, which is illustrated here in polyfill form.
Array.prototype.$forEach = function $forEach(callback) {
for (var index = 0; index < this.length; index++) {
var item = this[index];
// We're only interested in callback side-effects.
callback(item, index, this);
}
};
var result = 0;
[1, 2, 3, 4, 5].$forEach(function (item) {
result += item;
});
console.log('$forEach result', result); // 15
However, in terms of the example given for the usage of the Array.prototype.forEach()
polyfill, there actually is a more appropriate method to use. This is Array.prototype.reduce()
. Reduce allows us to locally scope the initial value and manipulate it within the method, rather than needing to initialize it external to the method and rely on side effects to do our work.
Array.prototype.$reduce = function $reduce(callback, accumulator) {
for (var index = 0; index < this.length; index++) {
var item = this[index];
// We're operating directly on the accumulated value with each iteration.
accumulator = callback(accumulator, item, index, this);
}
return accumulator;
};
var result = [0, 1, 2, 3, 4, 5].$reduce(function (acc, item) {
return acc += item;
}, 1);
console.log('$reduce result', result); // 16
So those are a few examples of how writing polyfills for core JavaScript methods can lead to some interesting insights. In this case, we discovered that the Array.prototype
methods we looked at really have a lot more in common internally with each other than we might think, given their very different external behavior. The differences can be summed up in how each one uses the return value of their callback
argument, or, in the case of Array.prototype.forEach()
, the side effect of the callback
.