Extending Built-in Objects in JavaScript

by kirupa   |   12 December 2013

As you know very well by now, JavaScript comes from the factory with a good supply of built-in objects. These objects provide some of the core functionality for working with text, numbers, collections of data, dates, and a whole lot more. As you become more familiar with JavaScript and start doing interesting-er and cleverer things, you'll often find that you want to do more and go farther than what the built-in objects allow.

Let's take a look at an example of when something like this might occur. Earlier, in the Shuffling an Array tutorial, you saw how to shuffle the contents of an array:

function shuffle(input) {
    for (var i = input.length - 1; i >= 0; i--) {

        var randomIndex = Math.floor(Math.random() * (i + 1));
        var itemAtIndex = input[randomIndex];

        input[randomIndex] = input[i];
        input[i] = itemAtIndex;
    }
    return input;
}

The way you used this shuffle function was by simply calling it and passing in the array whose contents you want shuffled:

var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
shuffle(tempArray);

// and the result is...
alert(tempArray);

After this code has run, the end result is that the contents of your array are now rearranged. Now, this functionality is pretty useful. I would say this is sooo useful, the shuffling ability should be a part of the Array object and be as easily accessible as push, pop, slice, and other doo-dads the Array object has.

If the shuffle function were a part of the Array object, you could simply use it as follows:

var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
tempArray.shuffle();

This is an example of us extending a built-in object (the Array) with some functionality that we defined (the shuffle). In this tutorial, we are going to look at how exactly to accomplish this, why it all works, and why extending built-in objects is pretty controversial.

Onwards!

Say Hello to prototype...again. Sort of!

Extending a built-in object with new functionality sounds complicated, but it is really simple once you understand what needs to be done. To help with this, we are going to look at a combination of sample code and diagrams all involving the very friendly Array object:

no idea why this is here

Um...anyway, let's say that we have the following code:

var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

If we were to diagram the full hierarchy of the tempArray object, it would look as follows:

the prototype chain for Array

On the left, we have our tempArray object that is an instance of Array. The built-in Array object is derived from the basic Object type. Now, what we want to do is extend the Array object with our shuffle function. What this means is that we need to figure out a way to get our shuffle function inserted into our Array object itself:

where the shuffle function needs to go

Here is the part where the quirkiness of JavaScript shines through. We don't have access to the Array object source code. We can't find the function or object that makes up the Array and insert our shuffle function into it like we might for a custom object that we defined. Your built-in objects, such as the Array, are defined deep inside your browser's volcanic underbelly where no human being can go. We need to take another approach.

That another approach involves casually sneaking in and attaching your functionality by using the Array object's prototype property. That would look something like this:

Array.prototype.shuffle = function () {
    var input = this;

    for (var i = input.length - 1; i >= 0; i--) {

        var randomIndex = Math.floor(Math.random() * (i + 1));
        var itemAtIndex = input[randomIndex];

        input[randomIndex] = input[i];
        input[i] = itemAtIndex;
    }
    return input;
}

Notice that our shuffle function is declared on Array.prototype! As part of this attachment, we made a minor change to how the function works. The function no longer takes an argument for referencing the array you need shuffled:

function shuffle(input) {
     .
     .
     .
     .
     .
}

Instead, because this function is now a part of the Array, the this keyword inside the function body points to the array that needs shuffling:

Array.prototype.shuffle = function () {
    var input = this;
     .
     .
     .
     .
}

Taking a step back, once you run this code, your shuffle function will find itself shoulder-to-shoulder with all of the other built-in methods the Array object provides:

the shuffle is here

If you wanted to access the shuffle function (err...method!), you can now do so using the approach we had initially desired:

var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tempArray.shuffle();

This is because the prototype property provides you with direct access to your Array's insides. Declaring the shuffle function on it gave us the result we wanted. Best of all, any new arrays you create will also have access to the shuffle functionality by default thanks to how prototype inheritance works.

Extending Built-in Objects is Controversial

Given how easy it is to extend a built-in object's functionality by declaring methods and properties using the prototype property, it's easy to think that everybody loves the ability to do all of this. As it turns out, extending built-in objects is a bit controversial. The reasons for this controversy revolve around...

You Don't Control the Built-in Object's Future

There is nothing preventing a future implementation of JavaScript from including its own version of shuffle that applies to Array objects. At this point, you have a collision where your version of shuffle and the browser's version of shuffle are in conflict with each other - especially if their behavior or performance characteristics wildly differ. Rut roh!

Some Functionality Should Not Be Extended or Overriden

Nothing prevents you from using what you've learned here to modify the behavior of existing methods and properties. For example, this is me changing how the slice behavior works:

Array.prototype.slice = function () {
    var input = this;
    input[0] = "This is an awesome example!";

    return input;
}

var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tempArray.slice();

// and the result is...
alert(tempArray);

While this is a terrible example, this does show how easy it was for me to break existing functionality.

Further Reading

To see a more comprehensive discussion and further reading around this controversy, check out this StackOverflow thread.

Conclusion - What Should You Do?

My answer to what you need to do is simple: Use your best judgment!. The two cases I outlined are only part of the various issues that people raise when extending built-in objects is discussed. For the most part, all of the objections are valid. The question you need to ask is, "Are these objections valid for my particular scenario?" My guess is that they probably won't be.

From personal experience, I have never had any issues extending built-in objects with my own functionality. I wrote this shuffle function years ago, and no browser as of now has even hinted at implementing their own version of it. I am certainly not complaining! Second, for any functionality I do add, I test to make sure that it works well across the browsers I am currently targeting. As long as your testing is somewhat comprehensive (probably the latest one or two versions of the major browsers), you should be good to go.

If you are worried about future-proofing, name any properties or methods in a way that only your app would use it. For example, the chances of Array.prototype.kirupaShuffle being introduced by any future browser release is pretty close to zero :P

Did You Like This?

Getting Help

If you have questions, need some assistance on this topic, or just want to chat - post in the comments below or drop by our friendly forums (where you have a lot more formatting options) and post your question. There are a lot of knowledgeable and witty people who would be happy to help you out

Share

Did you enjoy reading this and found it useful? If so, please share it with your friends:

If you didn't like it, I always like to hear how I can do better next time. Please feel free to contact me directly at kirupa[at]kirupa.com.

Cheers!

Kirupa Chinnathambi

 

Add Your Comment (or post on the Forums)

add your comment

  

Read-only Archive of Old comments

Below is an archive of old comments made on this article. To create new comments click on the Start or Continue Discussion text above to add to this list.

blog comments powered by Disqus

Creating high-quality content is a team effort that takes a boatload of time. If you found what you see here helpful, please consider sending a small tip:

While tipping is entirely optional, we'll be your bestest friend forever if you do.

More Details & Options