Demystifying JavaScript Constructor Functions (and prototypes)

January 2015

Magic?

A lot of people there is a lot of "hidden" magic in JavaScript. While that might be true for some parts it most definitely is not for prototypical inheritance (and constructor functions)

What is going on with is kind of code, and why does it work?

function Person(name, email) {
    this.name = name;
    this.email = email;
}

Person.prototype.speak = function () {
    console.log(this.name, "says:", "'speak i do'");
};


var frank = new Person("frank","frank@home.com");
frank.speak();

In this article i would to show you that constructor functions are really just based on two basic concepts in object-orientated JavaScript.

First two concepts

Concept I: Objects are dynamic

I am well aware that most people will be aware of this. It is however an important part of understanding constructor functions in JavaScript.

So let's get this one over quickly:

var person = {name : "jennifer"};

console.log(person);

person.email = 'jenny@work.com'; //

console.log(person);

When you run this, you will see:

$ /usr/bin/node demo.js

 { name: 'jennifer' }
 { name: 'jennifer', email: "jenny@work.com" }

So when a property (email) does not exist yet on an object while assigning a value to it, you add the property to the object. (As you know you can also delete most properties using delete person.name)

Check "one", let's continue.

Concept II: Context of a function can be set

Perhaps not all people know that the this-reference (aka 'context') of a function can be set when calling. JavaScript in the browser uses this extensively for setting the context for an event handler:

<body>
    <button id="btn1">click me</button>
    <button id="btn2">click me</button>
</body>
<script>
    function disableThis() {
        this.disabled = true;
        console.log('disabled', this.id)
    }

    var btns = document.getElementsByClassName("button");
    document.getElementById('btn1').addEventListener('click', disableThis);
    document.getElementById('btn2').addEventListener('click', disableThis);
</script>

The this in the disableThis references the button you clicked. This means your browser is capable of setting the context of a function.

This is achieved using one of the function's methods Function.prototype.apply() or Function.prototype.call() (they only differ in parameters, apply accepts an array for the arguments of the function you are calling, while call using a varying arguments.

Take a look at this rather silly example:

function logger(msg){
    this.log(msg);
}

logger.call(console,"hello");

What we are doing here is calling the logger function as a "method'. The first argument of call specifies the value for the 'this'-reference inside the function. In the example above i am passing the console object. So when this.log(...) is invoked, it is calling 'log on console .

Putting one and two together

Let's combine the two concepts from above and use it to create an initialisation function:

function initPerson(name, email){
    this.name = name;
    this.email = email;
}

When invoking this method with a context (iow using call/apply) we can set the value of the this reference (concept II). And whichever object that is, will get two new properties due to the dynamics of properties (concept I). (The previous sentence assumes the object did not have name and email yet. In that case it would merely change the value. ) Whatever the object looked like before calling/applying, it will have definitely name and email properties with the values passed into the initPerson function:

var jenny = {};
initPerson.call(jenny,"jennifer","jenny@work.com");

console.log(jenny);
// yields { name: 'jennifer', email: 'jenny@work.com' }

Cool! You might already notice we are getting closer, however you would only create such an initialisation function to make your code more DRY. Meaning there must be multiple times you use it:

var narendra = {};
var ole = {};
initPerson.call(narendra,"narendra","narendra@work.com");
initPerson.call(narendra,"ole","ole@school.com");

Let's make it slightly easier to use by making the function return this

var fabian = initPerson.call({},"fabian","fabian@home.com");
console.log(fabian);

And let's put that all in a nice function:

function initPerson(name, email){
    this.name = name;
    this.email = email;
    // no longer needs to return this.
}

function createPerson(name, email) {
    var newobj = {};
    return initPerson.call(newobj,name,email);
}

var moaz = createPerson("moaz","moaz@school.com");

The problem is that we have a bunch of objects (jenny, narendra and ole) which all share the same properties and most likely behaviour. They however are not related to a "group". A group of the same type of objects is typically called a class. (Fruit is a class in a botanical classification system to which oranges and tomatoes belong. Funny enough are oranges and tomatoes not classified the same in a retail classification system. In such a taxonomy tomatoes belong to the class of "vegetables"). Enough about "food" (another class of products ;)

In JavaScript a class of objects is defined by having the same prototype in the prototype chain.

Prototype and Concept III

The Prototype

The term "prototype" confuses many people (especially for those coming from class-based languages such as C++, C# and Java). There is no magic in prototypes. They are simple as "abc".

Let investigate:

var  ancestor= {name:'jennifer'};

var child = Object.create(ancestor);

console.log("#1","child name",child.name);
console.log("#2","child", child);

ancestor.name='bill';
console.log("#3","child name",child.name);

child.name='cindy';
console.log("#4","child", child);
console.log("#5","child name",child.name);

The Object.create creates a new object (child) and sets the prototype of this new object to specified one (ancestor).

(The diagram also shows a second child object on which the name was not set )

What is the outcome of each step>

  1. This yields: "#1 child name jennifer". This is why: when asking child for "its" name property, the "object looks" around its room as says: "Heck i don't have a name property. Shoot! Better ask my prototype". And of course its prototype ancestor does have a property name.
  2. Shows "#2 child {}". This just illustrates what was stated above: the child does not have a name property (its prototype has)! (Side note: the in operator would yield true when asking 'name' in child. This is more a problem than a feature. You might have seen when iterating through properties the check for hasOwnProperty)
  3. After changing the name of the ancestor to bill, the "child's name" (as we know this is not really the "child's name") yields #3 child name bill. No magic, still the same: we ask child, child looks around: whoops no name let's ask my ancestor, which is now known as bill.
  4. What we really did when "assigning" cindy to the name of the child, is adding a new property to child using the dynamics explained above in "concept I". Therefore when printing child we now see "#4 child { name: 'cindy' }".
  5. Yields the expected "#5 child name cindy"

Sharing a prototype

In order to define a "class of objects" in JavaScript we must just make sure they have the same prototype:

Let's define a common ancestor:

var ancestor = {name: 'no name', alive : true};

ancestor.speak = function(){
    console.log(this.name,"says:","'speak i do'");
};

We can now create new objects with the same properties and behaviour:

var zumba = Object.create(ancestor);
zumba.name = 'zumba';

var frederic = Object.create(ancestor);
frederic.name = 'frederic';

zumba.speak();
frederic.speak();

Both zumba and frederic both have name, are alive and have the ability to speak out.

While they really are similar and do belong to the same group, there is no easy way of finding out they are of the same "type". Well with the exception of checking the prototype values.

It would be nice if we could use instanceof

Adding a common prototype to our createPerson

Let's update our createPerson function further:

// the good old initPerson
function initPerson(name, email){
    this.name = name;
    this.email = email;
}

// The ancestor with speak behaviour
var ancestor = {};
ancestor.speak = function(){
    console.log(this.name,"says:","'speak i do'");
};

// our new factory function
function createPerson(name, email) {
    newObj = Object.create(ancestor);
    return initPerson.call(newObj,name,email);
}

var fang = createPerson("fang", "fang@home.com");
fang.speak();

So this is putting together what we know thus far. When we call createPerson it creates a new object with ancestor as its prototype. It then passes this new object to the initPerson function as a method, but setting the context of the function using call.

Finding a good prototype object

In JavaScript there are not real "instances" as it does not have classes (yet) but only objects. FYI: The word instance is always followed by the prepositions 'of' and then the name of a class (the definition of "instance" is an example or single occurrence of something). It is therefore strange for a language without instances to have the keyword instanceof (guess some more influence from the Java programming language).

If you look at the code so far you might wonder what is the purpose of the ancestor object? It is only used internally by creation process. This is also true for the initPerson function. It is only helping the creation process.

Let's work on these redundancies. Let's first focus on the ancestor. This might become a strange road. Now what we need is some common prototype object. Any object will do, right? What other objects to we have in the example thus far? There are a few candidate objects which you might not have considered! You should know that functions are objects as well in JavaScipt. If you don't believe it; we invoked methods on it: call:

// As a reminder:
initPerson.call(newObj,name,email)

So initPerson is actually also an object! A plain old JavaScript object (may i borrow the acronym 'pojo' from Java?).

So if initPerson is a pojo:

// the good old initPerson
function initPerson(name, email){
    this.name = name;
    this.email = email;
}

// Let's add a method to the initPerson object!
initPerson.speak = function(){
    console.log(this.name,"says:","'speak i do'");
};

// our new factory function
function createPerson(name, email) {
    // pass the initPerson object as the prototype
    newObj = Object.create(initPerson);
    initPerson.call(newObj,name,email);
    return newObj;
}

var fang = createPerson("fang", "fang@home.com");
fang.speak(); // still works

This is getting crazy. But think about it: initPerson is just another object. We have just replaced ancestor with initPerson.

We have a problem

We do have a slight problem. Any person we create has the same methods as their prototype. In this case their prototype is initPerson. You can think of a few methods on that object which don't make sense to people:

if (fang.call){
    console.log("fang has a call method!")
}

Whoops. Yes all the members of a function are exposed to people through the prototype-chain.

But we can go further. If initPerson is an plain old object , then it has a prototype just like any other object. Let's use that for our "ancestor":

initPerson.prototype.speak = function(){
    console.log(this.name,"says:","'speak i do'");
};


function createPerson(name, email) {
    newObj = Object.create(initPerson.prototype);
    initPerson.call(newObj,name,email);
    return newObj;
}

var fang = createPerson("fang", "fang@home.com");
fang.speak();

You might not realise how close we are. Perhaps the following code will illustrate just how close we are:

if (fang instanceof initPerson)
    console.log("yes fang is an 'instance' of initPerson");

Let's make it even feel closer and just refactor the name of the initPerson to an uppercase Person:

// the good old initPerson, now Person
function Person(name, email) {
    this.name = name;
    this.email = email;
}

// Use the (now named) Person's prototype
Person.prototype.speak = function () {
    console.log(this.name, "says:", "'speak i do'");
};


function createPerson(name, email) {
    newObj = Object.create(Person.prototype);
    Person.call(newObj, name, email);
    return  newObj;
}

That looks awfully similar to the initial code with the constructor at the top of this page.

We are so close to just replacing :

var noki = createPerson("noki", "noki@home.com");

with

var noki = new Person("noki", "noki@home.com");

In other words: our createPerson is doing almost what new does! (yes read that again)

There is just one missing link missing, a silly property value

The "coming together" (with the silly property)

This post could be over, but there is still one thing new does which our createPerson does not do.

Apart from having a prototype prototype, objects can also have a constructor property. Again no magic there. The constructor property just points to the constructor function which was used to create the object.

We can do that:

function createPerson(name, email) {
    newObj = Object.create(Person.prototype);
    newObj.constructor =  Person;
    Person.call(newObj, name, email);
    return newObj;
}

Et Voila!

And as a reminder here is the code we have so far:

// the  Person (formerly known as 'initPerson')
function Person(name, email) {
    this.name = name;
    this.email = email;
}

// Adding methods to the Person
Person.prototype.speak = function () {
    console.log(this.name, "says:", "'speak i do'");
};


function createPerson(name, email) {
    newObj = Object.create(Person.prototype);
    newObj.constructor =  Person; // was the missing link
    Person.call(newObj, name, email);
    return newObj;
}

At this moment the following two code blocks do the same thing:

var phil = createPerson("phil", "phil@home.com");
var mikhail = new Person("mikhail", "mikhail@uni.com");