Nitesh Narang's Blog

Nitesh Narang's Blog

Understand Map, Filter, and Reduce Through Javascript Polyfills

Understand Map, Filter, and Reduce Through Javascript Polyfills

If you are the one that always gets confused about when to use what method, this blog is right for you.

Writing polyfills makes you understand things more deeply. You will know what's going wrong and whatnot. It gives us good software engineering skills to have. I will discuss few polyfills which are widely used, and I try to explain them in simpler terms. Such that it is easier for you to grasp it and practice it on your own.

Map

The map is a method that iterates over the list and gives us the ability to modify each item. It returns the new list instead of doing an array in-place mutation. In simpler terms, the Map method returns the new reference of the list with modified items.

Working Of Map:

const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

The map method is taking an callbackFunction as the argument. This callbackFunction is getting called for each item. That means, It's looping over each item and pushing the return value to an empty array.

Map Rough Implementation

const array1 = [1, 4, 9, 16];
function myMap(array, callback) {
  let newArray = [];
  for (let i = 0; i < array.length; i++) {
    newArray.push(callback(array[i]));
  }
  return newArray;
}
const map1 = myMap(array1 ,x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

The problem with the current definition of myMap is I have taken the array as a parameter, and also there is no index value taken into consideration.

However, the OG map callbackFunction has 3 parameters: item, index, array. You can read more about here.

OG Map Working

const map2 = [2, 8, 18, 32].map((item,index,array) => {
    console.log(index, ',', array);
    return item * 2
})
console.log(map2)
// expected output: 
0 , [2,8,18,32]
1 , [2,8,18,32]
2 , [2,8,18,32]
3 , [2,8,18,32]
[4, 16, 36, 64]

Let's try to implement our own map

Map Polyfill

// Polyfill for the map
Object.defineProperty(Array.prototype, "myMap", {
  value: function (callback) {
    let newArray = [];
    let i = 0;
    while (i < this.length) {
      newArray.push(callback(this[i], i, this));
      i++;
    }
    return newArray;
  }
});

// TESTS
const map2 = [2, 8, 18, 32].myMap((item,index,array) => {
    console.log(index, ',' , array);
    return item * 2
})
console.log(map2)
// expected output: 
0 , [2,8,18,32]
1 , [2,8,18,32]
2 , [2,8,18,32]
3 , [2,8,18,32]
[4, 16, 36, 64]

Going Through The Code

Basically, We are using Object.defineProperty to create our own version of the map which behaves as the OG map, and while getting the array from the array.myMap() we have used the this object. We are calling callback(this[i], i, this) function with 3 arguments just like OG map.

To read more about how Object.defineProperty works and what it is read here.

Filter

The filter is a method that iterates over the list and gives us the ability to return those items which are true in the callback function. It returns the new list of filtered items.

Working Of FIlter:

const words = [
  "spray",
  "limit",
  "elite",
  "exuberant",
  "destruction",
  "present"
];

const result = words.filter((word) => word.length > 6);

console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

The filter method is taking an callbackFunction as the argument. This callback function is getting called for each item. That means, It's looping over each item and pushing only those items that return true in callbackFunction

Filter Rough Implementation

function myfilter(array, callback) {
  let newArray = [];
  for (let element of array) {
    if (callback(element)) {
      newArray.push(element);
    }
  }
  return newArray;
}
const result = myFilter(words, (word) => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

The problem with the current filter implementation is the same as Map Rough Implementation

Filter Polyfill

// Polyfill for the filter
Object.defineProperty(Array.prototype, "myFilter", {
  value: function (callback) {
    let newArray = [];
    let i = 0;
    while (i < this.length) {
      if (callback(this[i], i, this)) {
        newArray.push(this[i]);
      }
      i++;
    }
    return newArray;
  }
});

// TESTS
const result2 = words.myFilter((word) => word.length > 6);

console.log(result2);
// expected output: Array ["exuberant", "destruction", "present"]

Going Through The Code

Basically, We are using Object.defineProperty to create our own version of the filter which behaves as the OG filter, and while getting the array from the array.myFilter() we have used the this object. We are calling callback(this[i], i, this) function with 3 arguments just like OG filter.

To read more about how Object.defineProperty works and what it is read here.

Reduce

The reduce is a method that iterates over the list and gives us the ability to do an accumulation of all the items into a single value. Basically, it returns a single value that results from reduction.

Working Of Reduce:

Example 1:

let a = [10, 21, 13, 56];

function add(a, b) {
  return a + b;
}

const result = a.reduce(add)

console.log(result);
// expected output: 100
  • Iteration 1 : add(10, 21) => 31
  • Iteration 2 : add(31, 13) => 44
  • Iteration 3 : add(44, 56) => 100

The reduce method is taking an callbackFunction as the argument. This callbackFunction is getting called for each item in a list. callbackFunction contains two parameters that are accumulator and currentValue. Accumulator is the one that gets the result value, while currentValue is the one that gets each item value in a list.

In the above example, we are not providing the initialValue to accumulator, But we can do that, see the next example.

Example 2:

let a = [10, 21, 13, 56];

function add(a, b) {
  return a + b;
}

const result = a.reduce(add,10) // 2nd Argument of reduce method is for initial value

console.log(result);
// expected output: 110
  • Iteration 1 : add(10, 10) => 20
  • Iteration 2 : add(20, 21) => 41
  • Iteration 3 : add(41, 13) => 54
  • Iteration 4 : add(54, 56) => 110

In the 2nd Example, we have provided the initialValue to the accumulator, so that's why the currentValue is starting from the index 0.

In the 1st Example, we haven't provided an initialValue to the accumulator. So accumulator took the index 0 as the initialValue, while the currentValue got the index 1 value.

Reduce Rough Implementation

let b = [10, 21, 13, 56];
function add(a, b) {
  return a + b;
}
function foo(a, b) {
  return a.concat(b);
}
function myReduce(array, callback, initial) {
  let i = 0;
  let acc = initial;
  if (arguments.length < 3) {
    i = 1;
    acc = array[0];
  }
  while (i < array.length) {
    acc = callback(acc, array[i]);
    i++;
  }
  return acc;
}
console.log(myReduce(b, add), b.reduce(add)); // 100 100
console.log(myReduce(b, add, 10), b.reduce(add, 10)); // 110 110
console.log(myReduce(b, foo, "X"), b.reduce(foo, "X")); // X10211356 X10211356

I am checking with arguments.length < 3 that I have got the initialValue or not. If I didn't get any initial value, I am changing the currentValue index to 1, and assigning the accumulator to array[0]. if I have got the initialValue, it won't execute the if condition and took default values of accumulator that is initial and currentValue will start with index 0

The problem with the current reduce implementation is the same as Map Rough Implementation

Reduce Polyfill

// Polyfill for the reduce
Object.defineProperty(Array.prototype, "myReduce", {
  value: function (callback, initial) {
    let i = 0;
    let acc = initial;
    if (arguments.length < 2) {
      i = 1;
      acc = this[0];
    }
    while (i < this.length) {
      acc = callback(acc, this[i], i, this);
      i++;
    }
    return acc;
  }
});
// Tests
let a = [10, 21, 13, 56];
function add(a, b) {
  return a + b;
}
function foo(a, b) {
  return a.concat(b);
}
console.log(a.myReduce(add), a.reduce(add)); // 100 100
console.log(a.myReduce(add, 10), a.reduce(add, 10)); // 110 110
console.log(a.myReduce(foo, "X"), a.reduce(foo, "X")); // X10211356 X10211356

Going Through The Code

Basically, We are using Object.defineProperty to create our own version of the reduce which behaves as the OG reduce, and while getting the array from the array.myReduce() we have used the this object. We are calling callback(acc, this[i], i, this) function with 4 arguments just like OG reduce.

To read more about how Object.defineProperty works and what it is read here.

Things To Keep In Mind

Though these implementations of polyfills will work fine. But, I haven't taken the edge cases into considerations. For edge cases, we have to put some checks, and you can refer to that from the official MDN docs. They have already written well-tested polyfills, while the logic behind the implementation is still the same.

Connect with me on Twitter, LinkedIn.

 
Share this