The KIRUPA orange logo! A stylized orange made to look like a glass of orange juice! Tutorials Books Videos Forums

Change the theme! Search!
Rambo ftw!

Customize Theme


Color

Background


Done

Table of Contents

Extending Built-in Objects in JavaScript

by kirupa   |    filed under JavaScript 101

As we 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. Below is an example of how we can shuffle the contents of an array:

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

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

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

The way we use this shuffle function is by simply calling it and passing in the array whose contents we want shuffled:

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

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

After this code has run, the end result is that the contents of our 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, we could simply use it as follows:

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

This is an example of us extending a built-in object (the Array) with some functionality that we defined (the shuffle). In the next few sections, 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:

let 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:

On the left, we have our tempArray object that is an instance of Array.prototypewhich is, in turn, an instance of the basic Object.prototype. Now, what we want to do is extend what our array is capable of 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.prototype:

Here is the part where the quirkiness of JavaScript shines through. We don't have access to the code that makes up all of the array functionality. 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. Our built-in objects, such as the Array, are defined deep inside our 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 our functionality to the Array object's prototype property. That would look something like this:

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

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

        let randomIndex = Math.floor(Math.random() * (i + 1));
        let 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 () {
    let input = this;
     .
     .
     .
     .
}

Taking a step back, once we run this code, our shuffle function will find itself shoulder-to-shoulder with all of the other built-in methods the Array object exposes through Array.prototype:

If we wanted to access the shuffle capabilities, we can now do so using the approach we had initially desired:

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

Best of all, any new arrays we 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 Overridden

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 () {
    let input = this;
    input[0] = "This is an awesome example!";

    return input;
}

let 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

Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Creating engaging and entertaining content for designers and developers since 1998.

Follow:

Popular

Loose Ends