Pollyfill and some of its implementations

So polyfills huh? What is a polyfill exactly?

According to MDN

A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it.

What it means is, suppose you have .map or .find implementation in your code but a particular browser doesn't support it, what do you then? You then write a piece of code the browser can fall back on if the original implementation is not supported by it. So, a polyfill essentially mimics the functionality of a standard javascript function/operation.

Now that we know what a polyfill is, lets move into some of the simple and common implementations of it.

In this article, we'll cover the polyfills of:

  1. .map()
  2. .reduce()
  3. .bind()


Important keywords:

Before we start, we need to first understand how some of the helper keywords behave because they will enable us in creating a polyfill that works as intended. I will attach links that you can refer to in case you need a refresher on them.

  1. this
  2. prototype


Polyfills:

To make things easier to understand, for each of the polyfills, we will work with an objective in mind.

1. .map():

Our Objective: To get the age, increment it by 1, and return a new array having the incremented age numbers.

Here, we have an array called "user" where our goal is to take the age from the age property of it and increment the value by 1.

The first step is to make an array prototype and name it myMap. Now this prototype that we created will work on all the arrays since we initialized it as an Array.prototype.

Moving forward, let's assign a function to myMap and this function will take a callback function as its parameter. The role of the callback is to perform the required operation as described by us (here, incrementing the age by 1) for each element of the array.

.map() iterates through all the elements inside an array, so to replicate this, we will use a for loop for our myMap prototype function.

Also, since we know that each element takes in (element, index, array) as its parameters for .map(), we need to pass the same as the parameters to our callback function.

To accomplish this, we use the this keyword here. The functionality of the "this" keyword here is given below:

  • this.length: Refers to the length of the whole array (here, 3)
  • this[i] : Refers to the elements in the array (here: 27, 17, 24).
  • i : Refers to the index of the array
  • this: Refers to the whole array (here, users)

Now, as we know that .map() is a pure function, we need to return a new array once all the operation is done by the callback function. For this, we define a new array inside our myMap prototype method and call it arrayToReturn. So, once the callback function performs all the required operation, we push the returned value (from the callback), to the "arrayToReturn" array.

Finally, once the for loop inside the prototype function myMap finishes, we return the arrayToReturn array

const users = [
  {name: "John", age:26},
  {name: "Tom", age:16},
  {name: "Sally", age:23},
]

Array.prototype.myMap = function (callback){
  let arrayToReturn = []
    for(let i=0; i<this.length; i++){
      arrayToReturn.push(callback(this[i], i, this))
  }
  return arrayToReturn
}

const newArr = users.myMap(el=> el.age+1)
console.log(newArr)  // [ 27, 17, 24 ]


2. .reduce():

Our Objective: To get an array of age less than 25.

Here, for .reduce() the idea remains almost the same as that of .map() as mentioned above.

The key change here is the introduction of the accumulator to mimic how the .reduce() method behaves.

To initialize the accumulator (similar to how we do for .reduce()), the prototype function myReducer takes in an extra parameter called initialVal (along with the callback function) and then the value of the initialVal parameter is assigned to the accumulator variable inside our prototype function.

Now for each iteration, the returned value of the callback function is assigned to the accumulator and at the end, when the for loop is finished, the final accumulator value is returned.

const users = [
  {name: "df", age:26},
  {name: "ddd", age:16},
  {name: "frrefg", age:23},
  undefined
]

Array.prototype.myReducer = function (callback, initialVal){
    let accumulator = initialVal
    for(let i=0; i<this.length; i++){
      accumulator = callback(accumulator, this[i], i, this)
    }
  return accumulator
}

const reducedArr = users.myReducer((acc, curr)=> {
  if (curr.age < 25){
    acc.push(curr.age)
    return acc
  }else{
    return acc
  }
}, [])

console.log(reducedArr) // [16, 23]


3. .bind():

Let us define two objects namely data and data2.

Our Objective: bind data 2 with the print function property of data1.

For .bind(), we define the prototype as a Function prototype and name it as myBind. We are using Function.prototype since bind works on functions and it returns a function that can be stored in a variable for later use.

Now, our myBind method takes in the 2 parameters. First is the foreign object (data2) which needs to bind to the function ( here "data.print" ) and the second parameter is a placeholder for if any arguments passed while binding the foreign object.

Inside the myBind function, we can see an obj variable which is assigned the value "this". This "this" points to data.print.

Once we have the external wiring in place, now, since .bind() returns a function, we bind data2 to data.print using apply function.

For the arguments used in .apply(),

  • objdata: refers to data2
  • ...args1: arguments passed along with data2 (if any) when myBind was called on data2 (here: none)
  • ...args2: arguments passed from the function where myBind was stored
    (here: 15, "female" from returnedFunc )

Once this is done, we essentially have a working bind polyfill in place.

let data = {
  name: "John Doe",
  age: 30,
  gender: "male",
  print: function (age, gender){
    console.log("Person Data: ", this.name + ", " + age + ", " + gender)
  }
}

let data2 = {
  name: "Jane Doe",
  age: 25,
  gender: "female",
}


Function.prototype.myBind = function(objdata, ...args1){
  let obj = this
  return function (...args2){
     obj.apply(objdata, [...args1, ...args2])
  }
}


let returnedFunc = data.print.myBind(data2) 
console.log(returnedFunc(15,"female"))

With this, I hope you got a brief idea of what polyfills are and how some of these work. Surely there are optimizations that can be done to the polyfills mentioned here but let's keep that for another day. See you soon.