Table of contents
- What is a polyfill?
- Polyfill for Array.prototype.reduce()
- Case 1: Check if this exists, reduce is not called on null or undefined
- Case 2: Check if callback is a function
- Case 3: Reduce on empty array with no initialValue should throw error
- Case 4: Invoked on empty array with initialValue, returns initialValue
- Case 5: Invoked with initialValue
- Case 6: Invoked without initialValue
- Complete code
The full source code for the polyfill is available an the end ⬇️
What is a polyfill?
A polyfill is a piece of code used to provide modern functionality on older browsers that do not natively support it.
Polyfill for Array.prototype.reduce()
We will be writing the polyfill in steps, and in each step we will cover a test case
.
Note: In first 3 cases we will handle the error conditions
Case 1: Check if this
exists, reduce is not called on null or undefined
To write the polyfill, we will create a function called myReduce
which will take two arguments which are, a callBack
function and the initialValue
namely.
function myReduce(callback, initialValue) {...}
In the current case, this
refers to the array on which our myReduce
is called.
For example,
[1, 2, 3].myReduce(...)
then, in the above example this
refers to the array [1, 2, 3].
moving ahead, in the current case, we have to check if this
exists and reduce is not called on null or undefined, and if it is, then throw a TypeError ☠️.
So, the code for it will look like:
function myReduce(callback, initialValue) {
if (this === null || this === undefined) {
throw new TypeError(
'Array.prototype.myReduce is called on null or undefined'
);
}
}
Case 2: Check if callback
is a function
To handle this case, we add another check for callback, and throw a TypeError if otherwise.
function myReduce(callback, initialValue) {
if (this === null || this === undefined) {
throw new TypeError(
'Array.prototype.myReduce is called on null or undefined'
);
}
if (!callback || typeof callback !== 'function') { // check for callback to exist, and it should be a function
throw new TypeError(`${callback} is not a function`);
}
}
Case 3: Reduce on empty array with no initialValue should throw error
To handle this case, we add another if check, and throw a TypeError if otherwise.
function myReduce(callback, initialValue) {
if (this === null || this === undefined) {
throw new TypeError(
'Array.prototype.myReduce is called on null or undefined'
);
}
if (!callback || typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
if (!this.length) { // i.e. if array is empty
if (arguments.length < 2) { // i.e. if there are less than two arguments, then that means `initialValue` is missing, since callback is a mandatory argument for myReduce function.
throw new TypeError('Reduce of empty array with no initial value');
}
}
}
Note: Read more about arguments here
Case 4: Invoked on empty array with initialValue, returns initialValue
For this, we add an else if block, and check if there are exactly 2 arguments
Note: for the sake of readability, I will be only showing the the code under discussion in code snippets.
function myReduce(callback, initialValue) {
...
...
if (!this.length) { // i.e. if array is empty
if (arguments.length < 2) { // i.e. if there are less than two arguments
throw new TypeError('Reduce of empty array with no initial value');
} else if (arguments.length === 2) { // This check means that there are exactly 2 arguments, which will namely be -> `callback` and `initialValue`
return initialValue; // so, in that case return initialValue
}
}
}
For example,
function sum(a, b) {
return a + b
}
[].myReduce(sum, 5) // here `sum` is the callback function and 5 is the initialValue.
// OUTPUT
5
Case 5: Invoked with initialValue
We initialize 2 variables:
k
equal to 0
and, acc
equal to initialValue
(acc is short for accumulator, because it will be used to accumulate the result)
acc
is set to initialValue
because we want our accumulated result to already have the initialValue
in it.
Note: You may be wondering why are we using the
var
keyword to declare variables (which is usually a discouraged practice in modern web development), well since the whole point of a polyfill is that it should run in old browsers that don't support latest features yet, hence we are usingvar
which has been a part of JS since the start.
We also start a while loop, which runs until k
is less than the array's length, and inside the while loop we update the acc
variable to callback(acc, this[k], k, this)
and increment k
Now, callback(acc, this[k], k, this)
might seem complex at first, so let me break it down.
By definition, a callback function in reduce has to take 4 parameters:
- accumulator (acc)
- currentValue (this[k] i.e. the current value of of the array under iteration)
- currentIndex (k)
- array (
this
)
function myReduce(callback, initialValue) {
...
...
...
var k = 0;
var acc = initialValue;
while (k < this.length) {
acc = callback(acc, this[k], k, this);
k++; // increment k
}
return acc;
}
For example,
function sum(a, b) {
return a + b
}
[1, 2, 3].myReduce(sum, 1) // here 1 is the initialValue.
// OUTPUT
7
Case 6: Invoked without initialValue
For this case, we check if arguments.length
is less than 2, because it means that initialValue
has not been provided, and if it is less than two then we equate the accumulator to this[k++]
, which means the first value of the array (this[0], since k = 0 initially). We also post-increment k
by using k++
, because we want the while loop to start from index 1 and not from index 0 since the accumulator already has the initialValue as the value at index 0 in the array.
function myReduce(callback, initialValue) {
...
...
...
var k = 0;
var acc = initialValue;
if(arguments.length < 2){
acc = this[k++] // update `acc` and increment `k`
}
// rest of the code stays the same
while (k < this.length) {
acc = callback(acc, this[k], k, this);
k++;
}
return acc;
}
For example,
function sum(a, b) {
return a + b
}
[1, 2, 3].myReduce(sum) // no initialValue provided.
// OUTPUT
6
Complete code
function myReduce(callback, initialValue) {
if (this === null || this === undefined) {
throw new TypeError(
'Array.prototype.myReduce is called on null or undefined'
);
}
if (!callback || typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}
if (!this.length) {
if (arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
} else if (arguments.length === 2) {
return initialValue;
}
}
var k = 0;
var acc = arguments.length < 2 ? this[k++] : initialValue;
while (k < this.length) {
if (Object.prototype.hasOwnProperty.call(this, k)) {
acc = callback(acc, this[k], k, this);
}
k++;
}
return acc;
}
This blogpost is inspired by this youtube video which does a great job at explanation.
Thankyou for reading this article! Let's become friends on twitter🐤 also :)