Objects in JavaScript - Part I

The JavaScript world is going through a lot. Long gone are the days of plain jQuery DOM manipulation and minimal templating with Handlebars. There is an abundance of revolutionary frameworks, easy to use tools, pertinent opinions, calculated paradigm shifts, cleverly coined terms and I could go on. The JS community is embracing this diversity, proving that it has an infinite amount of enthusiasm, and probably a bit too much patience for its own good. However, for an outsider this world may look like a complete mess.

We all know the joke of the JavaScript: The Good Parts being such a light book compared to JavaScript: The Definitive Guide, but this captures perfectly the nature of the JavaScript language, and the conflicting opinions around it.

Brendan Eich came up with the first draft of the JS language specification in little more than a week. He tried mixing the best parts of the imperative and functional programming, having as resulting a very flexible language. Combine this flexibility with an incredible wide adoption, and you’ll understand all the debate around both basic features and high level frameworks.

Here is a Nymphomaniac scene for you… (Via)

Today’s debate: Is JavaScript Object Oriented? The answer is yes. But should you use it in an object oriented programming style? The answer is… not really.

The object as a stand alone entity that holds state and behavior is the only notion linking JS to the entire mindset of object orientation from languages such as Java or Ruby. Hoping to impress girls with your bike as you’d do with a fancy new car on the account that they both have wheels will lead you to quite disappointing conclusions. Trust me, I know. In the same way, expecting JavaScript to behave like a well established object oriented language since it also has objects is probably a bit optimistic.

To put things into perspective, we’ll go through the four core concepts of OOP, and figure out how these apply to the most popular language on earth.

Objects & Classes

Up until 2015, JS had a simple constructor mechanism to create objects:

function Pirate(name, rank) {
  var rank = rank;
  this.name = name;
  this.say = function() { console.log(`${rank} ${name}!`) };
}
let jack = new Pirate('Jack Sparrow', 'Captain');

console.log(typeof Pirate);         // function
console.log(typeof jack);           // object
console.log(jack instanceof Pirate) // true
console.log(jack.rank);             // undefined
console.log(jack.name);             // 'Jack Sparrow'

You’ll notice that a constructor is just a plain function. Everything attached to this will be passed to the resulting object instance, and calling Pirate() with the new keyword enables the function’s special constructor powers. Constructor functions were the established technique to build objects, until ES2015 classes were introduced:

class Pirate {			
  constructor(name, rank) {
    this.rank = rank;
    this.name = name;
  }
  say() {
    console.log(this.rank + ' ' + this.name + '!');
  }
}		
let jack = new Pirate('Jack Sparrow', 'Captain');

console.log(typeof Pirate);           // function
console.log(jack instanceof Pirate);  // true

The class keyword is mainly syntactical sugar, since Pirate is still a function at its core. However, you’ll find out that the JS Classes specification comes with some new features and rules. For instance, you can invoke class Pirate only using the new keyword:

let jack = Pirate('Jack Sparrow', 'Captain');
// Uncaught TypeError: Class constructor Pirate cannot be invoked without 'new'

For an in depth look at ES2015 Classes, spare a few minutes to check Axel Rauschmayer’s detailed article on the subject.

The real syntactic sugar is brought by object literals which simplify the object creation process, but they have their drawbacks when it comes to code reusability:

let jack = {
  name: 'Jack Sparrow',
  rank: 'Captain',
  say: function() { console.log(jack.rank + ' ' + jack.name + '!') }
}

Whichever technique you prefer, you’ll find out that neither the constructor functions nor the classes guarantee a final structure on the resulting object. Concepts such as C#’s partial classes or Ruby’s open classes offer a lot more flexibility than a constraining class model such as Java’s. However, JS allows programmers to alter object structures by adding state and behavior directly to the instance, even at run time. This leads to scenarios in which objects sharing the same class may have little structure in common, making the JS objects conceptually alike to dictionaries.

let jack = new Pirate('Jack Sparrow', 'Captain');
let elizabeth = new Pirate('Elizabeth Swann', 'Newbie');

jack.fight = function() { console.log('No thanks!') };
elizabeth.gender = 'female';

console.log(jack.gender);       // undefined
console.log(elizabeth.gender);  // 'female'
jack.fight();                   // 'No thanks!'
elizabeth.fight();              // TypeError: elizabeth.fight is not a function

The previous example shows the error prone coding style you can easily run into due to JavaScript’s flexibility. In all fairness, attaching methods directly to an instance is usually frown upon since it doesn’t make use of the prototypal nature of the language. There may be cases when this style has its uses, but most of the time you’d probably be interested in behavior reusability, also known as inheritance.

Inheritance

This is where JS starts differentiating itself from some other well established object oriented languages, mainly because it solves behavior reusability through prototype-based programming techniques. Unlike the usual class inheritance most of us are used to, JavaScript objects inherit the properties of their constructor’s prototype. This approach adds yet another layer of flexibility to the language, but can also be a source of errors and performance issues.

First, let’s take a look at the properties of two different jack instances, one created through a constructor function and one created through a constructor class:

class Pirate {			
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
}
let clsJack = new Pirate('Jack Sparrow');
for (let prop in clsJack)
  if (clsJack.hasOwnProperty(prop)) console.log(prop);
// Output: name

function Pirate(name) {
  this.name = name;
  this.say = function() { console.log(this.name) };
}
let fncJack = new Pirate('Jack Sparrow');
for (let prop in fncJack)
  if (fncJack.hasOwnProperty(prop)) console.log(prop);
// Output: name, say

Even though the two used techniques should have alike results, the instance obtained through invoking a constructor function contains the say property while the class instance doesn’t. This happens because methods defined in class Pirate are attached to the Pirate.prototype object by default. In contrast, function Pirate attaches its methods to the resulting instance itself.

hasOwnProperty helps differentiating between instance and prototype properties. It is easy to figure out that having the say method attached to each of the Pirate objects is not a good behavior reusability decision. To fix this, let’s update the constructor function to make sure that any Pirate method is instead attached to Pirate.prototype. This change is straight forward, since all functions have a prototype property linking to an object. This object is passed to all resulting instances when the function is called as a constructor:

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

// Define methods at the prototype level
Pirate.prototype.say = function() { console.log(this.name) };

let jack = new Pirate('Jack Sparrow');
jack.say(); // 'Jack Sparrow'
for (let prop in jack)
  if (jack.hasOwnProperty(prop)) console.log(prop);
// Output: name

This small change has actually a great impact since all objects will have access to the say method through prototypal inheritance from now on. Furthermore, any new method added to the prototype, even at run time, will be automatically visible in all the Pirate instances.

Let’s analyze a more detailed prototype based inheritance example:

function Actor(realName) {
  this.realName = realName;
}
Actor.prototype = {
  say: function() { console.log('The actor'); },
  greeting: function() { console.log('I am ' + this.realName); }
}

function Pirate(name, realName) {
  Actor.call(this, realName);
  this.name = name;
}

// Share Actor's prototype with Pirate
Pirate.prototype = Object.create(Actor.prototype);
// Ensure constructor link validity
Pirate.prototype.constructor = Pirate;
Pirate.prototype.say = function() { console.log('The pirate'); };

let johnny = new Actor('Johnny');
let jack = new Pirate('Jack', 'Johnny');		

console.log(johnny.greeting()); // 'I am Johnny'
console.log(johnny.say());      // 'The actor'
console.log(jack.greeting());   // 'I am Johnny'
console.log(jack.say());        // 'The pirate'

The previous code snippet shows a simple scenario in which Pirate inherits Actor’s behavior by sharing its prototype object. This results in a jack instance which has access to methods from both constructors. Under the hood, this access is resolved through the prototype chain. Each instance has direct access to the prototype object associated to the constructor used to create that instance. You can check this through the instance’s __proto__ accessor property:

jack.__proto__ === Pirate.prototype   // true
jack.__proto__.__proto__ === Actor.prototype  // true
jack.__proto__.__proto__.__proto__ === Object.prototype  // true
jack.__proto__.__proto__.__proto__.__proto__ === null // true - end of the chain

This prototype chain backs the lookup process used to access instance properties. Every time a property is accessed, the prototype chain is traversed bottom to top in search for the specific property. Applied to our current example, since the jack instance itself doesn’t have the greeting method, the engine will look next in Pirate’s prototype method - with no result. Next, Actor’s prototype will be inspected and its greeting method will be used.

// jack inherits from Pirate.prototype
// Pirate.prototype inherits from Actor.prototype
// Actor.prototype inherits from Object.prototype

jack.greeting();

// lookup jack - no greeting property - move up the chain
// lookup Pirate.prototype - no greeting property - move up the chain
// lookup Actor.prototype - property found - use it

It should be obvious that trying to access properties deep in the prototype chain or non existing properties - when the entire chain will be traversed with no result - could cause performance issues in an ill thought architecture. Furthermore, while working with function prototypes is encouraged, the __proto__ property is much more sensitive business.

Another special instance property is the constructor property which inherited from top of the chain’s Object.prototype. When creating the object, the constructor also links itself to the instance. In our example, since inheritance may break this link, we made sure that it is still valid through: Pirate.prototype.constructor = Pirate;. Even though this is not mandatory, it could save you a lot of headache with object copying and instanceof checks.

With the advent ES2015, our example could be refactored as follows:

class Actor {
  constructor(realName) {
    this.realName = realName;
  }
  say() {
    console.log('The actor');
  }
  greeting() {
    console.log('I am ' + this.realName);
  }
}

class Pirate extends Actor {
  constructor(name, realName) {
    super(realName);
    this.name = name;
  }
  say() {
    console.log('The pirate');
  }
}

Granted, this approach looks less hacky, mainly because it hides all the prototype complexity. However, keep in mind that hiding complexity is all that the ES2015 class brings to the table. Under to hood, things are still the same, and knowing all the prototypal inheritance details we went through is a must in order to make sense of the subject.


In part two of this article, among others, we will look into encapsulation, and the JavaScript’s take on the matter. However, it should be clear by now that you don’t have your usual objects, classes and inheritance in JavaScript. To confirm this, the community is arguing that the introduction of the class notion last year would confuse programmers that are new to JS even more, since they may have a specific set of expectations and ideas from other object oriented languages.