Javascript Polyfills: forEach, map, filter and reduce

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 tha…


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 returns undefined.
  • callbackFn must be called in the context of thisArg. If thisArg is not passed, callbackFn is invoked as a regular function.
  • If a callback function is not passed as the first argument, forEach throws a TypeError.
  • If forEach is invoked on null or undefined, it throws a TypeError

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:

  1. The second argument to reduce is an optional initialValue, used to initialize previousValue.
  2. Value returned from callbackFn after traversing all elements of the array is ultimately returned from reduce.
  3. If initialValue is not provided, previousValue is initialized to the first element in the array, and reduce begins traversal from the second element in the array.
  4. If array is empty and initialValue is not provided, a TypeError 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 assigned undefined. We can check for this and throw a TypeError with the appropriate error message.

  • Is a callback function passed?

  • Is reduce called on null/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


Print Share Comment Cite Upload Translate Updates
APA

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/

MLA
" » Javascript Polyfills: forEach, map, filter and reduce." S Shraddha | Sciencx - Saturday October 23, 2021, https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/
HARVARD
S Shraddha | Sciencx Saturday October 23, 2021 » Javascript Polyfills: forEach, map, filter and reduce., viewed ,<https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/>
VANCOUVER
S Shraddha | Sciencx - » Javascript Polyfills: forEach, map, filter and reduce. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/
CHICAGO
" » Javascript Polyfills: forEach, map, filter and reduce." S Shraddha | Sciencx - Accessed . https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/
IEEE
" » Javascript Polyfills: forEach, map, filter and reduce." S Shraddha | Sciencx [Online]. Available: https://www.scien.cx/2021/10/23/javascript-polyfills-foreach-map-filter-and-reduce/. [Accessed: ]
rf:citation
» Javascript Polyfills: forEach, map, filter and reduce | S Shraddha | Sciencx | 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.

You must be logged in to translate posts. Please log in or register.