JavaScript Prototypal Inheritance and What ES6 Classes Have to Say About It

JavaScript Prototypal Inheritance and What ES6 Classes Have to Say About ItMany people come to JavaScript from other object-oriented programming languages such as Java or C++ and are confused as heck. "Where are the classes?" Well JavaScript doesn't have classes. Rather, JavaScript uses prototypal inheritance to create something similar to classes. Though it is somewhat similar, it's still quite different and takes a lot of work to understand. That's the purpose of ES6 Classes.

I recently went over how objects and prototypal inheritance works in JavaScript, so I won't go into too much detail about it here, but I will point out a bunch of the caveats of the way things are done in JavaScript today.

Current Prototypal Inheritance

Prototypal inheritance isn't terribly difficult to get started using for simple things, but it gets increasingly difficult (and difficult to understand) when you try to move beyond simple. Looking at the example below (courtesy of Nicholas Zakas because I was too lazy to write my own simple code), when creating the Animal type, you see some oddness, but once you get over that, it's not difficult. For instance, to create the constructor, we just make a function name Animal. That's odd, but you get over it and it's not much of a problem. The only real problem here is that unless everyone follows conventions correctly, it's difficult to know when someone is just writing a function or if they're writing a constructor. Conventions in naming help, though.

Anyway, moving on, we see how to add a method to Animal that will be available to all of its instances. Unless you're quite familiar with JavaScript, the prototype keyword might seem a bit foreign to you, but once again, as soon as you get used to using it, it's not much of a hurdle. The only thing wrong is readability, as usual, but any seasoned JavaScript developer will recognize it.

function Animal(name) {
    this.name = name;
}

Animal.prototype.sayName = function() {
    console.log(this.name);
};

Now we're going to get to the troublesome area. In the code below, we're going to create a type that inherits from Animal called Dog. Two things stick out to me as "bad". On line 2, we try to call the "super" constructor, but since there's no super keyword or anything relatively similar, we need to use a not-so-well-known feature of JavaScript, namely call or apply. While the existence of these two functions has started to become better known, they're still an advanced feature that beginners are unlikely to know. It definitely took me a while to learn about them. In any case, it isn't elegant.

The second nuisance comes from trying to establish the inheritance, which is done with this code below: Dog.prototype = new Animal(null);. Even now, this code doesn't make a whole lot of sense to me. I understand what's going on, but creating an instance of a type so that you can inherit from it doesn't make sense. Also a potential bug could show up if the Animal constructor does anything besides initialize internal properties, such as DOM manipulation. All we're trying to do is inherit from Animal but to do so an Animal is created and starts changing the page.

function Dog(name) {
    Animal.call(this, name);
}

Dog.prototype = new Animal(null);

Dog.prototype.bark = function() {
    console.log("Woof!");
};

Because of these apparent problems, many libraries created their own way of handling this inheritance that simplifies it. There is nothing inherently wrong with the prototypal inheritance model. The problems come from the work required to do it and the misunderstanding that can come from its syntax.

Another Way

JavaScript is a functional programming language (though not only functional), which is part of the reason this has been so difficult up to this point. There's another way to do inheritance without prototypes that plays more toward JavaScript's functional nature that relies entirely on object literals and functions to do all of the work. It's a very interesting alternative, but it pulls even further away from what Java and C++ programmers are used to. If you're interested, Toby Ho has a great article explaining JavaScript OO without constructors.

The ES6 Way

In ECMAScript 6, we'll see the introduction of "classes". Many, many people argue that they are unnecessary, and technically they are. ES6 classes aren't even classes; they are syntactic sugar that eases our development. That's all. Classes aren't an entirely new construct in JavaScript, they are just a new way of saying the exact same thing, except it makes more sense and is simpler to use. Take a look at the following code that rewrites the previous example using ES6 classes:

class Animal {
    constructor(name) {
        this.name = name;
    }
 
    sayName() {
        console.log(this.name);
    }
}
 
class Dog extends Animal {
    constructor(name) {
        super(name);
    }
 
    bark() {
        console.log("Woof!");
    }
}

If you came from a background with a classical OO language, wouldn't this make perfect sense to you? It's clear, concise, and simple. You get exactly what you think you're getting. Also, though the syntax is new, there is actually nothing new going on here. It is still doing the same thing as before, just with cleaner code. There's still prototype chaining, methods are still added to prototypes, you can still manipulate it the same way you did with the old syntax, but you don't have to anymore.

Conclusion

Do we need to have classes in JavaScript? No, but they definitely clean up the way we define types and do inheritance in JavaScript, which can never be a bad thing. It also helps developers coming from other languages learn about JavaScript a little more easily. The only problem with everything I've seen with ES6 is that there won't be a decent amount of browsers compatible with it for quite some time. But that's where things like TypeScript come in.

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • Radomír Žemlička

    You shouldn’t make an inheritance by creating a new instance of Animal (I mean you shouldn’t call the constructor). You should just create object referencing to the Animal’s prototype (for example by using the Object.create function).

    • http://www.joezimjs.com Joe Zimmerman

      If you never call Animal’s constructor (in the example we called it) from within the Dog constuctor, and Animal’s constructor creates properties, then you need to call that constructor somehow to get those properties set. It is standard practice to call the constructor and assign it to the prototype. If the constructor doesn’t do anything useful, you could also just assign the prototype: Dog.prototype = Animal.prototype;. Calling object.create limits you to ES5 capable browsers and has the same problem as just using Dog.prototype = Animal.prototype.

      • Jeff M

        > Dog.prototype = Animal.prototype

        That would be very bad. Now any methods you attach to Dog’s prototype will also be attached to Animal’s prototype, because you’ve made them the same thing. So bark(), which is supposed to be a Dog method, will now be an Animal method too.

      • Radomír Žemlička

        It isn’t actually what I meant. You should only call the Animal contructor in the Dog constructor. The dog’s prototype should reference only to the Animal’s prototype, not to its dynamic properties.

        function Dog(name) {
        Animal.call(this, name);
        }
        Dog.prototype = Object.create(Animal.prototype);

        And it’s not true you are limited to ES 5 browser. You can define the function for older browsers:

        if (!Object.create) {
        Object.create = function (obj) {
        var Fn = function () {};
        Fn.prototype = obj;
        return new Fn();
        }
        }

        • http://www.joezimjs.com Joe Zimmerman

          True, but the bigger point is that the new ES6 classes are that much simpler to work with. This debate doesn’t even matter anymore.

          • Jeff M

            Based on IE lifespans, I’d say we’ll have to wait _at least_ until 2016. Grrrr.

            • http://www.joezimjs.com Joe Zimmerman

              The best part about this is NodeJS. As soon as V8 supports ES6, NodeJS will support ES6. So at least we can use ES6 on the back end (assuming you use Node at least) without needing compile-to-javascript languages.

          • Radomír Žemlička

            You can already use ES 6 classes, for example by using TypeScript (created by Microsoft). It compiles to a plain ES 3 (or 5 if you need properties etc.) code.

            • http://www.joezimjs.com Joe Zimmerman

              Yes you can. :) Just an extra step in the build process, but if it’s automated, then who cares?

    • Jeff M

      Agree. Though, creating a new instance of Animal is how JavaScript inheritance was originally intended to work. Object.create is relatively new.

  • Angelo

    Just noticed that this page does not have any css style as it extends all the way the width of the page.

    • http://www.joezimjs.com Joe Zimmerman

      This page is working just fine for me right now.