Note: These polyfills were written to help me (and others) understand how JavaScript methods work internally. They’re not meant to be exact replacements for the native implementations.
Map, Filter and Reduce
Map
The map function accepts a single callback function as its argument.
We use a temporary array to store the new results.
The callback function receives three arguments — the current element, the current index, and the array itself.
Inside the callback, this refers to the array on which the map function is being called.
Array.prototype.devMap=function(cb){
let arr=[];
for(let i=0;i<this.length;i++){
arr.push(cb(this[i],i,this));
}
return arr;
Filter
The filter function accepts a single callback function as its argument.
Similar to the map function, we use a temporary array to store the filtered results.
Inside the callback, if the specified condition is met, the corresponding value is added to the new array.
Array.prototype.devFilter=function(cb){
let temp=[];
for(let i=0;i<this.length;i++){
if(cb(this[i],i,this)){
temp.push(this[i]);
}
}
return temp;
}
Reduce
Polyfill for the reduce function in JavaScript
The reduce function accepts a callback function and an initial value.
By default, the initial value (often referred to as iv) is assigned to the accumulator.
The callback function receives four arguments — the accumulator, the current element, the current index, and the array itself.
If the initial value is not provided, the first element of the array is used as the accumulator.
The results returned from each callback execution are then stored in the accumulator as the reduction progresses.
Array.prototype.devReduce=function(cb,iv) //iv-> initialValue
let acc=iv;
for(let i=0;i<this.length;i++){
acc=acc?cb(acc,this[i],i,this):this[i]; //this->refers to array
}
return acc;
}
Call ,Apply ,Bind
Call
Before exploring the call prototype function, let’s first understand its use case.
Suppose there is a function named getName and an object student that contains a key-value pair for name.
If we want to use the name value from the student object inside the getName function, we can achieve this by calling the function as getName.call(student).
When using call, the this keyword inside the getName function refers to the student object.
Without using call, this inside the getName function would refer to the global object.
function getName(){
console.log(`this function print name ${this.name}`};
}
const student={
name:"devyank"
}
getName.call(student);
Now, to create a polyfill for the call function, we need to understand that call accepts an object and additional arguments.
Since inside the function we can access the keys or properties of the provided object, we need to temporarily make that function a part of the object itself.
Polyfill for call
Function.prototype.devCall=function(ob,...args){
ob.fn=this;
ob.fn(...args);
}
Apply
The apply method is similar to call; the only difference is that the arguments are passed in the form of an array.
For example, in the example below, we have passed 15 as an age argument.
function getName(age){
console.log(`this function print name ${this.name} and ${age}`};
}
const student={
name:"devyank"
}
getName.call(student,15);
Polyfill for Apply
Function.prototype.devApply=function(ob,args=[]){
ob.fn=this;
ob.fn(...args);
}
Bind
This function is quite different from call and apply, as it returns a new function. However, its behavior is similar to that of the call function.
function getName(age){
console.log(`this function print name ${this.name} and ${age}`);
}
const student={
name:"devyank"
}
const bindFunc=getName.bind(student);
bindFunc(15);
Hence, for the bind polyfill as well, we assign the function to the object so that we can access its properties inside the function.
The only difference is that, in this case, we return a new function using bind instead of invoking it immediately.
Polyfill for Bind
Function.prototype.devBind=function(ob,...args){
ob.fn=this;
return function(...myargs){
return ob.fn(...args,...myargs);
}
}
Polyfill for Promise
To create a Promise polyfill, we first need to understand that a Promise has two internal functions — resolve and reject — and its instance provides two methods — then and catch.
There are two cases to consider: synchronous and asynchronous execution.
In the case of an asynchronous function, the value of onResolve is already a function. Therefore, inside the resolve function, we check whether onResolve is a function before calling it.
The callback functions passed to then and catch are assigned to onResolve and onReject, respectively.
In synchronous scenarios, onResolve and onReject are not yet functions when resolve or reject are called, so they are executed later inside the then and catch methods.
However, in asynchronous scenarios, the then and catch methods are executed before resolve or reject are triggered. As a result, the callbacks are already assigned to onResolve and onReject, allowing them to be called directly inside the resolve and reject functions.
function promisePolyfill(executor){
let onResolve,onReject,isFullfilled,isRejected,isCalled,value;
function resolve(val){
value=val;
isFullfilled=true;
if(typeof onResolve==='function'){
onResolve(val);
isCalled=true;
}
}
function reject(val){
value=val;
isRejected=true;
if(typeof onReject==='function'){
onReject(val);
isCalled=true;
}
}
this.then=function(cb){
onResolve=cb;
if(isFullfilled && !isCalled){
onResolve(value);
isCalled=true;
}
return this;
}
this.catch=function(cb){
onReject=cb;
if(isRejected && !isCalled){
onReject(value);
isCalled=true;
}
return this;
}
executor(resolve,reject);
}
Polyfills for Debouncing and Throttle
Debouncing
This function accepts a callback function and a delay time.
We maintain a variable timer to store the result returned by the setTimeout function.
The callback is executed after the specified delay.
If a timer already exists, we clear the previous timeout before setting a new one.
function useDebounce(fn,delay){
let timer;
return function(...args){
if(timer) clearTimeout(timer);
timer=setTimeout(()=>{
fn(...args);
},delay);
}
}
Throttling
In this approach, we maintain a variable last to store the last execution time.
We then check if the difference between the current time and the last execution time is smaller than the specified delay.
If it is, we simply return without calling the function; otherwise, we allow the function to execute.
function useThrottle(fn,delay){
let last;
return function(...args){
let now=new Date().getTime();
if(now-last<delay)return;
last=now;
return fn(...args);
}
}
Top comments (3)
Unfortunately, none of your array polyfills are valid polyfills for their native counterparts. The first two (map, filter) are missing the optional
thisArg
paramater, and none of them deal with sparse arrays correctly.Great catch — you’re absolutely right! These weren’t meant to be full spec-compliant polyfills, but rather a simplified version I built for learning and understanding the core logic behind methods like map and filter.
Still, I really appreciate the clarification about thisArg and sparse array handling — those are important details for a truly accurate polyfill. Thanks for taking the time to share your insight!
Then you're just adding similar methods, NOT polyfills. A polyfill is used to exactly duplicate functionality that is missing or unavailable in a specific environment for whatever reason.