Learning JavaScript deeply — Understanding filter
So in my last article I talked about my mission to learn JavaScript deeply and remove all the smoke and mirrors and take a peek under the hood of some functions to see the intricacies of how they work. The first function we looked at was forEach, you can read about that here.
In this article we’ll dissect the array method filer, and see how it works under the hood.
MDN, what is filter?
From the docs:
The filter()
method creates a new array with all elements that pass the test implemented by the provided function.
var newArray = arr.filter(callback[, thisArg])
From the syntax we can see that the filter function takes a callback and an option this argument. The callback takes the following arguments: (1) the current element in the array, (2) the current index of the element being processed and (3) the array that filter was being called upon.
Here’s an example:
var words = ["spray", "limit", "elite", "exuberant", "destruction", "present"];
var longWords = words.filter(function(word){
return word.length > 6;
})
// Filtered array longWords is ["exuberant", "destruction", "present"]
The code snippet above shows that we run the filter function on an array, and the function will return a new array based on the test specified in the callback function. So let’s try to deduce how this works:
- Problem: The function takes an array and performs a callback function on each item.
- Solution: We can use a for loop to iterate through the array and perform actions on each item
- Problem: The function returns a new array and does not mutate the original array
- Solution: We can create a new array in the function and return that array from the function
Now that we have a pretty good idea about how to solve the problem. Let’s try to build our own version. We won’t add it to the prototype array so our version will need to accept an array as the first argument.
Initial setup
We’ll start by defining an empty function definition that takes an array and a callback function:
function filter(array, callback, thisObject) {}
Then we’ll define the empty array that will be returned from the function:
function filter(array, callback, thisObject) {
var filteredArray = []; return filteredArray;
}
Then we’ll loop through the array given and perform the callback array.length number of times:
function filter(array, callback, thisObject) {
var filteredArray = []; for (var i = 0; i < array.length; i++) {
callback();
} return filteredArray;
}
Now let’s try an example:
filter([1, 2, 3], function(number) {
console.log(number); // undefined
});
As we can see our example returns undefined. This is because inside our filter function we are not yet passing any elements from the for loop into the callback function. In order to fix this we need to pass the ith element of the for loop into the callback.
function filter(array, callback, thisObject) {
var filteredArray = [];for (var i = 0; i < array.length; i++) {
callback(array[i]);
}return filteredArray;
}
Now that we have added the array[i] as a parameter inside our callback, our example will work.
filter([1, 2, 3], function(number) {
console.log(number); // 1, 2, 3
}); // returns []
We’ve made the function operate on each item, but we still can’t access the index of each item of the array inside the callback function. Let’s fix that now.
function filter(array, callback, thisObject) {
var filteredArray = [];for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}return filteredArray;
}
In order to make the index available in the callback function it is a simple matter of adding i as an argument to the callback inside our filter function. Let’s see how this works:
filter([1, 2, 3], function(number, index) {
console.log(index); // 0, 1, 2
}); // returns []
And now we’re able to access the index inside our callback and it correctly logs each items index to the console.
So far we’ve added the the element and the index of the element as arguments to our callback function. In order to complete the functionality for the callback function we just need to add the option to access the original array. So how do we do that? Easy:
function filter(array, callback, thisObject) {
var filteredArray = [];for (var i = 0; i < array.length; i++) {
callback(array[i], i, array);
}return filteredArray;
}filter([1, 2, 3], function(number, index, array) {
console.log(array); // [1, 2, 3]
});
We grab the array from the filter function and pass it into the callback function. Now we can access the original array inside the callback function.
Adding the option to set a this object
So let’s think about what we need to do. We want to set the this keyword equal to whatever we pass in as an argument to filter. So for example:
filter([1, 2, 3], function() {
console.log(this.name); // should log 'I am the this object';
}, {name: 'I am the this object'});
Looking at the example above. We want our function to use whatever object we pass as the third argument to the function to act as the this object. So how can we do this? Well, we’ve already done this in the forEach version that we built, but if you haven’t read that article there is a handy method on functions called bind. Bind will essentially return a new function with it’s this keyword set to whatever you specify as the argument to bind.
With this in mind let’s add the functionality to our code:
function filter(array, callback, thisObject) {
var filteredArray = [];
var filterCallback = callback;if (thisObject) {
filteredCallback = callback.bind(thisObject);
}for (var i = 0; i < array.length; i++) {
filteredCallback(array[i], i, array);
}return filteredArray;
}
So let’s walk through what we’ve done. First, we added a new variable called filteredCallback and set it equal to the callback. Then we created an if statement to check if thisObject is defined. If it is, we bind the callback to the thisObject and save it in filterCallback. Then we use filterCallback in the for loop. Example:
filter([1, 2, 3], function() {
console.log(this.name); // console logs 'I am the this object'
}, {name: 'I am the this object'});
Adding the filtering
So now all we have left to do is to add the functionality to filter the function. We know that we want the callback to be an expression that evaluates to true or false (how else would you filter something?). So we need to add some logic that takes a true or false value and performs some action based on it. Let’s use an if statement:
function filter(array, callback, thisObject) {
var filteredArray = [];
var filterCallback = callback;if (thisObject) {
filterCallback = callback.bind(thisObject);
}for (var i = 0; i < array.length; i++) {
if (filterCallback(array[i], i, array)) {
filteredArray.push(array[i]);
}
}return filteredArray;
}
Let’s walk through this last part. First we added an if statement around the filteredCallback, because we know that we want the callback to return a true or false value. If the statement returned from the callback evaluates to true, we want to push the array[i] item into the filteredArray. So what we end up with is a new array with only the items that pass the expression that we perform in the callback function.
filter([1, 2, 3], function(number) {
return number > 2;
}); // returns [3]
So in our final example, our function will return an array with one number in it, the number 3, since all other values in the original array will evaluate to false.
There you have it folks, we’ve dissected the array function filter and created our own version of it. And we’ve come out with a much better understanding of how it all works under the hood. Great success.
Follow me if you’d like to read more articles and insights on JavaScript as I go through a learning journey to learn JavaScript deeply.