This content originally appeared on DEV Community and was authored by S Shraddha
The Javascript language has been evolving steadily over the years. New features on the language appear regularly. Some older browsers may not support these modern functionalities.
A polyfill is a piece of code that implements a feature on browsers that do not support the feature. Hence, the name - it fills the gap by adding missing implementations.
This article focuses on writing polyfills for the popular Javascript Array methods - forEach, map, reduce and filter.
For details on the usage and syntax of these array methods refer MDN | Array instance methods.
.forEach( )
The forEach
method invokes the provided callback function for each element in the array.
Syntax
forEach(function callbackFn(element, index, array) { ... }, thisArg);
Some important things to note about forEach
:
-
callbackFn
is called on each element of the array. -
forEach
returnsundefined
. -
callbackFn
must be called in the context ofthisArg
. IfthisArg
is not passed,callbackFn
is invoked as a regular function. - If a callback function is not passed as the first argument,
forEach
throws aTypeError
. - If
forEach
is invoked onnull
orundefined
, it throws aTypeError
Let's begin with the first step:
if (!Array.prototype.myForEach) {
Array.prototype.myForEach =
function (callbackFn, thisArg) {
for (let i = 0; i < this.length; i++) {
callbackFn(this[i], i, this);
}
};
}
We first check if the function is already available in the prototype chain of Array
. this
inside the function references the array on which forEach
is called.
forEach
also accepts an optional second argument - thisArg
. If passed, the callback function must be invoked in the context of thisArg
i.e. this
inside callbackFn
must be set to thisArg
. This can be done using the call() method:
if (!Array.prototype.myForEach) {
Array.prototype.myForEach =
function (callbackFn, thisArg) {
for (let i = 0; i < this.length; i++) {
callbackFn.call(thisArg, this[i], i, this);
}
};
}
Time to handle the error cases!
- What if a callback function is not passed to
forEach
? - What if
forEach
is not invoked on an array?
In the above cases, an Error object must be thrown along with a descriptive message. Here, we will replicate the behavior shown by the original forEach
method.
if (!Array.prototype.myForEach) {
Array.prototype.myForEach = function (callbackFn, thisArg) {
if (this == null || this === window)
throw TypeError('Array.prototype.myForEach called on null or undefined');
if (typeof callbackFn !== 'function')
throw TypeError(`${callbackFn} is not a function`);
for (let i = 0; i < this.length; i++) {
callbackFn.call(thisArg, this[i], i, this);
}
};
}
this == null || this === window
- This condition is true if forEach is invoked as a standalone function (i.e not a method invocation). For example:
const myUnboundForEach = Array.prototype.myForEach;
myUnboundForEach();
myUnboundForEach()
is executed like a normal function expression. this
inside the callback function will be the global
object (window) in non-strict mode or undefined
in the strict mode. Both these conditions are handled above. In both cases the TypeError
is thrown.
And that's it! We have created our own implementation of the JS array method forEach
and have also handled the error conditions.
The polyfill implementation for the rest of the methods are very similar and only differ in the core functionality of the method.
.map( )
The map
method creates an array that contains values returned by the callback function invoked on each element in the calling array. Our function should now return the newly created array.
Syntax
map(function callbackFn(element, index, array) { ... }, thisArg);
Polyfill
if (!Array.prototype.myMap) {
Array.prototype.myMap = function (callback, thisArg) {
if (this == null || this === window)
throw TypeError('Array.prototype.myMap called on null or undefined');
if (typeof callback !== 'function')
throw TypeError(`${callback} is not a function`);
const mappedArray = [];
for (let i = 0; i < this.length; i++) {
const mappedValue = callback.call(thisArg, this[i], i, this);
mappedArray[i] = mappedValue;
}
return mappedArray;
};
}
.filter( )
The filter
method creates an array that contains only those elements of the calling array that pass the test provided by the callback function.
Syntax
filter(function callbackFn(element, index, array) { ... }, thisArg);
Polyfill
if (!Array.prototype.myFilter) {
Array.prototype.myFilter = function (callback, thisArg) {
if (this == null || this === window)
throw TypeError(
'Array.prototype.myFilter is called on null or undefined'
);
if (typeof callback !== 'function')
throw TypeError(`${callback} is not a function`);
const filtered = [];
for (let i = 0; i < this.length; i++) {
if (callback.call(thisArg, this[i], i, this)) filtered.push(this[i]);
}
return filtered;
};
}
.reduce( )
The reduce
method works a little differently than the above methods. It accepts a reducer callback function that is called on each element of the array along with the returned value from the previous invocation. After calling the reducer across all array elements, the single, accumulated result is returned.
Syntax
reduce(function callbackFn(previousValue, currentValue, currentIndex, array) { ... }, initialValue);
Some important things to note about reduce
:
- The second argument to
reduce
is an optionalinitialValue
, used to initializepreviousValue
. - Value returned from
callbackFn
after traversing all elements of the array is ultimately returned fromreduce
. - If
initialValue
is not provided,previousValue
is initialized to the first element in the array, andreduce
begins traversal from the second element in the array. - If array is empty and
initialValue
is not provided, aTypeError
is thrown.
Lets' begin with the main working of reduce
:
if (!Array.prototype.myReduce) {
Array.prototype.myReduce = function (callback, initialValue) {
let previousValue = initialValue;
let startIndex = 0;
if (initialValue == null) {
previousValue = this[0];
startIndex = 1;
}
for (let index = startIndex; index < this.length; index++) {
previousValue = callback(previousValue, this[index], index, this);
}
return previousValue;
};
}
This covers points 1, 2 and 3 above.
Time to handle the error cases:
What if
initialValue
is not provided and array is empty?
In this case,previousValue
will be assignedundefined
. We can check for this and throw aTypeError
with the appropriate error message.Is a callback function passed?
Is
reduce
called onnull
/undefined
?
All the above error cases are handled as follows:
if (!Array.prototype.myReduce) {
Array.prototype.myReduce = function (callback, initialValue) {
if (this == null || this === window)
throw TypeError('Array.prototype.myReduce called on null or undefined');
if (typeof callback !== 'function')
throw TypeError(`${callback} is not a function`);
let previousValue = initialValue;
let startIndex = 0;
if (initialValue == null) {
previousValue = this[0];
startIndex = 1;
}
if (previousValue == null)
throw TypeError('Reduce of empty array with no initial value');
for (let index = startIndex; index < this.length; index++) {
previousValue = callback(previousValue, this[index], index, this);
}
return previousValue;
};
}
Wrapping Up
We saw the working of some commonly used Array methods along with their polyfill implementation, while handling the error cases.
Thank you for reading. Happy coding! 🙂
This content originally appeared on DEV Community and was authored by S Shraddha
S Shraddha | Sciencx (2021-10-23T10:10:29+00:00) Javascript Polyfills: forEach, map, filter and reduce. Retrieved from https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.