Every new release of JavaScript (JS) includes new functions to achieve common programming tasks. Some of the newer additions include the Array indexOf() and DOM getElementsByClassName() functions. That being said, some browsers don’t keep up with the latest and greatest version of JavaScript or choose to implement their own version of similar functionality. To help smooth things over, developers often rely on libraries such as jQuery, Dojo, and Mootools. As great as these libraries are, there are times that you only need to beef up native JavaScript here and there. In those times, maybe you don’t need to load an entire library when you can just add the missing functions yourself. In today’s article, we’ll look at how to exploit existing functionality to develop an Array utility function that removes elements by value.
Covering your A…Bases
One of the most contentious practices in JavaScript development is the extending of native JS objects via the prototype property. In early JS books, authors were all but unanimous in advocating this practice, but over time, developers began to cry foul and cited numerous reasons why it should never be done. I don’t want to go all the reasons here, but I will say that some are certainly valid. I consider myself to be somewhat of a moderate where extending the prototype is concerned. I don’t extend Object.prototype, but I do extend specific objects such as Array and String. When doing so, I make sure to check for the existence of a function of the same name. If the function is already implemented, whether natively or via third-party library, I use it instead.
if (!Array.prototype.forEach) { //add my function }
Building on Array.splice()
The Array.splice() function is the model behind the Array.remove() function that I’ll be presenting here. Array.splice() operates directly on an array and returns a new array of removed items, which are matched by index number. A second parameter denotes how many elements to remove. It may also be utilized to add new items, but we won’t be making use of that feature here, so we’ll leave that part for another day.
Here’s an example of Array.splice() in action:
var cars = ["Buick", "Camaro", "Kia", "Infiniti"]; var removedCars = cars.splice(2,2); //After the above call, cars will be ["Buick", "Camaro"] //removedCars will contain "Kia" and "Infiniti"
Removing One Element
Our first interpretation behaves a lot like the Array.splice() function, but accepts an element value (object) instead of a numeric index. We could loop through the array, checking every element against our value, but there is already a function that does exactly that; it’s called Array.indexOf(). Similar to the String function of the same name, the Array.indexOf() accepts a value and returns the numeric index of the first occurrence. Hence, if the array contains more than one instance of the same value, only the first index is returned. If no matches are found, a value of -1 is returned. We can use that to only call Array.splice() when the array contains at least one instance of our search value. In the event that no matches are found, an empty array is returned. I don’t like returning undefined variables from functions because it increases the likelihood of errors. jQuery takes the same approach.
if (!Array.prototype.remove) { Array.prototype.remove = function(val) { var i = this.indexOf(val); return i>-1 ? this.splice(i, 1) : []; }; } var a = [1,2,3,2,4]; var removedItems = a.remove(2); //a = [1,3,2,4], removedItems = [2]; var b = [1,2,3,2,4]; removedItems = b.remove(8); //b = [1,2,3,2,4], removedItems = [];
Removing Multiple Elements
One thing lacking from Array.indexOf() is the ability to search for multiple items at once, so if we wanted to support that usage, we’d have to implement our own looping mechanism. That’s where the second version of Array.remove() comes in. This one accepts a second boolean parameter to indicate that we want ALL occurrences of matching values. There are two very important points to notice in the following for loop:
- It goes backward. Otherwise, the index will access the wrong element after removing an item.
- There is no checking clause. A funny thing about JS is that the loop will exit once the iterator counts down to zero.
if (!Array.prototype.remove) { Array.prototype.remove = function(val, all) { var i, removedItems = []; if (all) { for(i = this.length; i--;){ if (this[i] === val) removedItems.push(this.splice(i, 1)); } } else { //same as before... i = this.indexOf(val); if(i>-1) removedItems = this.splice(i, 1); } return removedItems; }; } var a = [1,2,3,2,4]; var removedItems = a.remove(2); //a = [1,3,2,4], removedItems = [2]; var b = [1,2,3,2,4]; removedItems = b.remove(2, true); //b = [1,3,4], removedItems = [2,2];
Matching Multiple Search Values
For our final implementation, we’ve upped the ante even more by allowing the user to search by more than one value. That means that a single value or an array may be supplied for the ultimate in flexibility. That might sound complicated, but thanks to the Array.isArray() function, we can easily distinguish between single and multiple search values. For the sake of consistency, single values are converted into an array of one element. That allows us to iterate over the search values whether there are one or fifty. That loop is accomplished using a conventional forward for loop. One other small change is the switch to removedItems.push() for storing single matches, again for consistency.
if (!Array.prototype.remove) { Array.prototype.remove = function(vals, all) { var i, removedItems = []; if (!Array.isArray(vals)) vals = [vals]; for (var j=0;j<vals.length; j++) { if (all) { for(i = this.length; i--;){ if (this[i] === vals[j]) removedItems.push(this.splice(i, 1)); } } else { i = this.indexOf(vals[j]); if(i>-1) removedItems.push(this.splice(i, 1)); } } return removedItems; }; } var a = ['1','2','3','2','4']; var removedItems = a.remove('4', true); //a = ['1','2','3','2'], removedItems = ['4']; var b = ['1','2','3','2','4']; removedItems = b.remove(['2','4'], true); //b = ['1','3'], removedItems = ['2','2','4'];
Conclusion
Generally, defining you own functions should be done as soon as possible during page loading. That usually means in the document <HEAD> section. Otherwise, you risk calling a function that doesn’t exist (yet)!