In JavaScript, we use functions in three different ways:

  • as methods of objects;
  • as constructors of objects;
  • and, as “regular” functions (that is, functions we typically write either as top-level functions, or functions we pass as anonymous functions to other functions).

I looked at the third use case for functions in my previous post, where I wrote about arrow functions.

Now, it’s time to take a look at the second of these use cases for functions: that is, as constructors of objects.

Functions that are used to construct objects are just like regular JavaScript functions, but we call them with the new operator, and it’s this new operator that makes them behave a bit differently from a normal function call. Let’s step through an example (and if you need a refresher on object construction, check out Chapters 12 and 13 in Head First JavaScript Programming.

Here’s a function we can use to construct Dog objects:

function Dog(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
    this.bark = function()
        console.log(this.name + " says woof woof!");
    };
}

To construct new objects using this function, we call the function with new, like this:

var fido = new Dog("Fido", "Mixed", 24);

Then we can access the Dog’s properties like this:

fido.name; // returns "Fido"
fido.bark(); // displays "Fido says woof woof!" in the console

If we want to put the bark method in the prototype object, we can do that like this:

Dog.prototype.bark = function bark() {
    console.log(this.name + " says woof woof!");
};

and remove the bark method from the Dog constructor function:

function Dog(name, breed, weight) {
    this.name = name;
    this.breed = breed;
    this.weight = weight;
}

Moving bark to the prototype object means that all new instances of Dog inherit the bark method but it’s only defined once (in the prototype object) and shared across all the instances (a bit more efficient).

We can also add methods to the function object, Dog. We didn’t talk about this in Head First JavaScript Programming, so if you’re not familiar with this already, don’t worry; this is just a way to define functions associated with Dogs, but that aren’t related to specific Dog instances. Here’s how you do that:

Dog.describe = function describe() {
   console.log("All dogs are canines");
};

You call describe like this:

Dog.describe();

Just remember that describe has nothing to do with Dog instances (like fido); it’s a method of the Dog constructor function.

So, Dog is our constructor function that we call with new to create new Dog objects; bark is a prototype method that’s inherited by all Dog instances, and describe is a function associated with the Dog constructor function (we often call this a “static” method).

Here’s the object diagram that’s created by this code:

ObjDiagram1.001

On the left side of the diagram, we’ve got the constructor function, Dog, a function object which inherits from the Function.prototype object, and has its own method, describe. On the right side of the diagram, we’ve got fido, the object we create when we call

var fido = new Dog("Fido", "Mixed", 24);

fido inherits the method bark from the Dog.prototype object, which in turn inherits methods like toString and hasOwnProperty (check out Chapter 13 in Head First JavaScript Programming for a refresher).

CLASS

In JavaScript ES6, we have a whole new way to write constructors, add methods to prototype objects, and define static methods. The key is the new class keyword in JavaScript. Now, before you get all excited, no, this doesn’t mean that JavaScript now has “classes” in the sense you might be used to if you’ve used Java or C# or another object-oriented language with “classical” inheritance. JavaScript is still a language with prototypal inheritance—that has not changed! The use of the keyword class to define constructors may be a bit confusing for those who are expecting traditional classes, but for those of you well-versed in prototypal inheritance, rest assured, that’s still the inheritance model in JavaScript.

Okay, now that we’ve got that out of the way, let’s see how you can use class—plus a couple of other new keywords—to define constructors. We’ll rewrite our Dog constructor using the new syntax for JavaScript ES6:

class Dog {
    constructor(name, breed, weight) {
        this.name = name;
        this.breed = breed;
        this.weight = weight;
    };
    bark() {
        console.log(this.name + " says woof woof!");
    };
    static describe() {
        console.log("All dogs are canines");
    };
};

One really nice thing about class is that we can now group all the parts of the constructor together: the constructor function itself, the prototype methods, and even the static methods. And notice that the function keyword is nowhere to be seen anywhere in this code!!

Okay, so how do you use this to create new Dog objects?

var fido = new Dog("Fido", "Mixed", 24);

In other words, it’s exactly the same! The fido object created by this code is no different from the fido object we created with the previous code. They are exactly the same. And, if we look at the object diagram that’s created by this code, you’ll see that’s the same too:

ObjDiagram2.001

In ES6, you’ll still be able to construct objects the “old” way. So what’s the advantage of using class instead? As I mentioned earlier, I think the syntax is a bit cleaner and easier to read and understand. But class really starts to shine when you want to do more than one level of inheritance: in other words, when you want to extend your objects further.

EXTENDING OBJECTS

If you’ve read Head First JavaScript Programming, then you’ll remember that we extended our Dog objects with ShowDog objects. These objects inherit from both ShowDog.prototype, a Dog object (to get name, breed, and weight), and from Dog.prototype to get the bark method. Here’s what that inheritance chain, or prototype chain, looks like in the book:

DogPrototypeChain

And the (ES5) code looks like this:

function ShowDog(name, breed, weight, handler) {
    Dog.call(this, name, breed, weight);
    this.handler = handler;
}
ShowDog.prototype = new Dog(); 
ShowDog.prototype.constructor = ShowDog;

ShowDog.prototype.stack = function stack() {
    console.log(this.name + " is stacking");
};
// other prototype methods here…

var fido = new Dog("Fido", "Mixed", 24);
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
scotty.bark();

To make sure that ShowDog instances inherit the methods in Dog instances, we have to set up the prototype relationship between ShowDog and Dog manually. Likewise, we have to make sure that the ShowDog.prototype.constructor property is set correctly.

The diagram below shows the object relationships created by this code. The red lines show the relationships that we have to set up manually:

ObjDiagram3.001

In JavaScript ES6, we’ll use class with another new keyword, extends, which takes care of all that for you:

class ShowDog extends Dog {
    constructor(name, breed, weight, handler) {
        super(name, breed, weight);
        this.handler = handler;
    };
    stack() {
        console.log(this.name + " is stacking");
    };
    // other prototype methods here…
};

The keyword extends indicates that we want ShowDog instances to inherit from Dogs. Inside the constructor, we call

super(name, breed, weight);

This takes the place of:

Dog.call(this, name, breed, weight);

in the ShowDog constructor done the old-fashioned way. super just means we’re accessing the “super class” (or super constructor) of ShowDog which is whatever object we’re extending, in this case Dog. This makes sure that name, breed, and weight are set correctly in the ShowDog.prototype object (just like before). We set the handler property in the constructor because handler is a property of ShowDogs, not Dogs.

Now, extends takes care of setting up the correct relationships between objects for us, using much more concise and clear syntax.

We create and use new ShowDog instances just like before:

var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
scotty.bark();
scotty.stack();

Here’s the object diagram for the code above using class and extends. Notice the red lines are gone, meaning that the relationships that we used to have to set up manually between the ShowDog.prototype and the Dog.prototype, and for the ShowDog constructor are now all black, meaning they are created for you. Also notice there’s one other arrow that wasn’t there in the previous diagram: the relationship between the ShowDog function object and the Dog function object:

ObjDiagram4.001

This extra arrow represents a relationship that wasn’t really possible to set up before and that is, an inheritance chain between the two function objects Dog and ShowDog. What this means is ShowDog (the function object) inherits the describe method from Dog (the function object). So you can call ShowDog.describe() and it will work—it will call the describe method in Dog. Probably not a huge deal but that might come in handy!

One last quick thing to note about extends. You saw that we can access the “super class” of ShowDog, Dog by using the keyword super. We can call the constructor of Dog by calling super as a function. We can also access properties and methods in the super class. To see that, let’s add a toString prototype method to both Dog and ShowDog:

class Dog {
    constructor(name, breed, weight) {
        this.name = name;
        this.breed = breed;
        this.weight = weight;
    };
    bark() {
        console.log(this.name + " says woof woof!");
    };
    // other prototype methods here…

    toString() {
        return "DOG: " + this.name + " weighs " + this.weight + " pounds.";
    };
};
class ShowDog extends Dog {
    constructor(name, breed, weight, handler) {
        super(name, breed, weight);
        this.handler = handler;
    };
    stack() {
        console.log(this.name + " is stacking");
    };
    // other prototype methods here…

    toString() {
        return "SHOW" + super.toString();
    };
};
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
scotty.toString();

In Dog, the toString method returns DOG, along with the name and weight of the Dog object.

In ShowDog, notice that we’re using super to access the toString method from Dog to create that string, and then we’re prepending the string SHOW on the front. The result is the string SHOWDOG, along with the name and weight of the ShowDog instance. If we write:

console.log(scotty.toString());

we’ll see

SHOWDOG: Scotty weighs 15 pounds.

in the console.

SUMMARY

This has been a whirlwind tour of using class to construct objects. The new syntax, along with the new keywords class, constructor, extends, super and static make constructing objects in JavaScript easier, as well as more concise and clear (and less error-prone as a result).

Let us know what you think about this new syntax. And, as I described in my first post in this series, until these features are implemented in all the major browsers, you can try them using the Traceur transpiler. Access the complete code for this example on github to see how to use Traceur with this example.

RESOURCES

We wrote the book: with over a 1,000,000 books sold, we’ve created some of the industries top-selling software & technology titles. We teach the experts: you find us teaching on today’s premier training sites, like Lynda.com and Safaribook’s Online, and we’ve created digital experiences for a small, but exceptional group of industry leaders and luminaries. Throughout it all, we’ve applied our wickedly smart philosophy to get you there faster with amazing results. How can we help you?

We wrote the book: with over a 1,000,000 books sold, we’ve created some of the industries top-selling software & technology titles. We teach the experts: you find us teaching on today’s premier training sites, like Lynda.com and Safaribook’s Online, and we’ve created digital experiences for a small, but exceptional group of industry leaders and luminaries. Throughout it all, we’ve applied our wickedly smart philosophy to get you there faster with amazing results. How can we help you?

Don't miss out!

Don't miss out on brain-friendly WickedlySmart updates early access to books and general cool stuff! Just give us your email and we'll send you something about once a week. 

You have Successfully Subscribed!