JavaScript Prototype and Object Oriented

Javascript is a prototype-based object-oriented programming language. Unlike the conventional class-based object-oriented programming languages (e.g. C++ or Java), which ask programmers to write a class and then instantiate several similar objects from the class, Javascript adopts a different approach. In the world of Javascript, we have to craft a prototypal object first and then create similar objects from the prototypal object. In this article, we would like to give a gentle introduction to object-oriented programming in Javascript.

Objects and Methods

Let's start to create our first object in Javascript:

var robert = {
  firstName: 'Robert',
  lastName: 'Smith',
};

In the code snippet above, we created a Javascript object holding Robert's first name and last name. Objects in Javascript are essentially dictionaries. We can access the properties with the dot operator. For example, the following code snippet prints Robert's first name and the last name:

console.log(robert.firstName);
// Prints: Robert
console.log(robert.lastName);
// Prints: Smith

Now, we can write a function to get the full name:

function getFullName(employee) {
  return employee.firstName + ' ' + employee.lastName;
}

console.log(getFullName(robert));
// Prints: Robert Smith

It works but it is suboptimal. Compared with getFullName(robert), it will be better to define a method for the object which was referred by the reference robert and invoke robert.getFullName() instead. To achieve this, let's move getFullName() into the object literal and replace employee argument with the implicit this argument:

var robert = {
  firstName: 'Robert',
  lastName: 'Smith',

  getFullName: function () {
    return this.firstName + ' ' + this.lastName;
  }
};

console.log(robert.getFullName());
// Prints: Robert Smith

The implicit this argument will be bound to the object before the dot operator at the callsite. In this case, it will be bound to the object which robert refers to.

Notice that this mechanism will only work if the dot operator and the function invocation are combined together. If we write these operators separately, then we will get a different result. For example, in the following code snippet, this will be bounded to the global host object of the Javascript run-time environment (window object in browsers and global object in Node.js) instead of the object which is referred by the reference robert:

var func = robert.getFullName;
console.log(func());
// Prints: undefined undefined

So far, we have learned to craft an object with several properties and methods. However, in the real world, many objects may share the same method. For example, getFullName() is not specific to robert. Are there any mechanisms to re-use this method?

Prototypal Inheritance

Javascript supports prototypal inheritance. With prototypal inheritance, a programmer can inherit properties and attributes from another prototypal object (a.k.a. prototype). To inherit from an object proto, we can invoke Object.create(proto) which will return a new object resembles to the object proto.

For example, the following code snippet creates a new object called mike which inherits from the object robert:

var mike = Object.create(robert);

mike.firstName = 'Mike';
mike.lastName = 'Williams'

console.log(mike.getFullName());
// Prints: Mike Williams

Notice that we can override the properties from the prototypal object in the derived object. In the example, we overrode firstName and lastName. Methods from the prototypal object (getFullName() in this example) can get the overridden properties through the this keyword.

Overriding properties will not change the prototypal object. For example, the firstName and lastName properties in robert object remains unchanged. The result of calling robert.getFullName() remains unchanged as well:

console.log(robert.firstName);
// Prints: Robert
console.log(robert.lastName);
// Prints: Smith
console.log(robert.getFullName());
// Prints: Robert Smith

However, changes to the prototypal object will be visible to derived objects if such property is not overridden by the derived object. For example, if we change the getFullName property in the robert object, then the result of calling mike.getFullName() will be changed:

robert.getFullName = function () {
  return this.lastName + ', ' + this.firstName;
};

console.log(robert.getFullName());
// Prints: Smith, Robert
console.log(mike.getFullName());
// Prints: Williams, Mike

We can get the prototypal object of an object with Object.getPrototypeOf(). For example:

console.log(Object.getPrototypeOf(mike));
// Prints: { firstName: 'Robert',
// Prints:   lastName: 'Smith',
// Prints:   getFullName: [Function] }

So far, we have discussed how to inherit properties and methods from another object. However, Object.create() is a function introduced in ECMAScript 5, which was released in 2011. How did people use prototypal inheritance before Object.create() was introduced?

Constructor

Before the introduction of Object.create(), the prototypal inheritance has to be done indirectly through the new operator and the constructor function.

New Operator and Constructor

The new operator will create a new object whose prototype is the object referred by the prototype property of the constructor function and pass the newly created object through the implicit this argument. Constructor functions are functions which assume that the implicit this argument will bind to a newly created object. These constructors will initialize the object by setting more properties. For example:

function Employee(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Employee.prototype = {
  getFullName: function () {
    return this.firstName + ' ' + this.lastName;
  },
};

var robert = new Employee('Robert', 'Smith');
var mike = new Employee('Mike', 'Williams');

console.log(robert.getFullName());
// Prints: Robert Smith
console.log(mike.getFullName());
// Prints: Mike Williams

The following code show that the prototype object of robert and mike is the object referred by Employee.prototype:

console.log(Object.is(Object.getPrototypeOf(robert), Employee.prototype));
// Prints: true
console.log(Object.is(Object.getPrototypeOf(mike), Employee.prototype));
// Prints: true

To get more insights, let's add a console.log() in the Employee constructor function:

function Employee(firstName, lastName) {
  console.log(Object.is(Object.getPrototypeOf(this), Employee.prototype));
  this.firstName = firstName;
  this.lastName = lastName;
}

new Employee('Ada', 'Lovelace');
// Prints: true

The result shows that the object referred by the implicit this argument is using Employee.prototype as the prototype object.

Constructor Pitfalls

We have learned about the new operator and the construction functions. However, please be aware that constructor functions are only functions following a specific convention. This abstraction will be broken if we don't call these functions through the new operator. For example:

var bob = Employee('Bob', 'Taylor');
// Prints: false
console.log(bob);
// Prints: undefined

In this code snippet, the implicit this argument will be bound to the global host object mentioned earlier, thus the prototype of the object won't be Employee.prototype. Besides, the value of bob will be undefiend becaue the Employee function does not return any value.

Return values of the constructor functions might cause some confusions as well. If a constructor does not return any value, then the new expression will become the newly created object referred by the implicit this argument as expected. However, if a constructor returns some value, then the new expression will become the returned value. For example:

function Employee(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  if (firstName === 'Bad' && lastName == 'Example') {
    return 'Surprise';
  }
}

console.log(new Employee('Robert', 'Smith'));
// Prints: { firstName: 'Robert',
// Prints:   lastName: 'Smith' }
console.log(new Employee('Bad', 'Example'));
// Prints: Surprise

This mechanism is designed to give the writer of constructor functions an opportunity to change the prototypal inheritance hierarchy. However, I personally don't use this feature in my code becuase it is too confusing.

Idiom for Constructor Functions

Now, let's get back to our first code snippet for Employee:

function Employee(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Employee.prototype = {
  getFullName: function () {
    return this.firstName + ' ' + this.lastName;
  },
};

In the our first version, we assigned an object to Employee.prototype. This is not necessary to create a new object if we are not planning to inherit from other prototypal objects. Javascript run-time will create an object and assign it to the prototype property of the function object when the function is defined. We can add properties to that object directly. Thus, the idiomatic implementation for Employee should be:

function Employee(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Employee.prototype.getFullName = function () {
    return this.firstName + ' ' + this.lastName;
};

var robert = new Employee('Robert', 'Smith');
console.log(robert.getFullName());
// Prints: Robert Smith

Craft a Polyfill

The new operator and the constructor function sound complex. How could I implement an Object.create() in ECMAScript 3? Here is a simple trick:

function create(proto) {
  function f() {}
  f.prototype = proto;
  return new f();
}

This code snippet defines a fucntion f locally, assign proto to prototype property, and return the object constructed by the new operator.

For example, we can copy the code snippet in the Prototypal Inheritance section and replace Object.create() with our version:

var robert = {
  firstName: 'Robert',
  lastName: 'Smith',
  getFullName: function () {
    return this.firstName + ' ' + this.lastName;
  }
};

var mike = create(robert);
mike.firstName = 'Mike';
mike.lastName = 'Williams';
console.log(mike.getFullName());
// Prints: Mike Williams

It works, but this implementation can be more efficient. We don't have to create a new function object f whenever we invoke create(). We can create one instance and reuse it:

var create = (function () {
  function f() {}
  function create(proto) {
    f.prototype = proto;
    return new f();
  }
  return create;
})();

In addition, we can wrap our polyfill with a check, so that we can use the built-in implementation if the Javascript run-time supports Object.create():

if (typeof Object.create !== 'function') {
  Object.create = (function () {
    function f() {}
    function create(proto) {
      f.prototype = proto;
      return new f();
    }
    return create;
  })();
}

Of course, our naive implementation is not fully compliant to the one specified in ECMAScript 5. Some features specified in ECMAScript 5 cannot be implemented with ECMAScript 3 unless some run-time specific hacks are applied. But, this polyfill helps us to relate Object.create() function with the constructor function.

Conclusion

In this post, we started from the object literals, properties, and methods. And then, we introduced the prototypal inheritance with Object.create(), which creates an object inheriting from another object. After having a solid idea on Object.create(), we went further to learn the new operator and constructor functions in ECMAScript 3. It was how people write object-oriented program in ECMAScript in the past. Finally, we connected two different methodologies by crafting an Object.create() polyfill. Hope you enjoy this post. See you next time.