Objects, Prototypes and Classes in JavaScript

Vijay Prasanna

Considering the fact that almost everything in JavaScript is an object, object oriented JavaScript code is very different from other object-capable languages. The JS object system is more of a prototype-based object system instead.

Coming from a C++ background, I was aware of the Object Oriented programming paradigm and had a very rigid idea of how Objects and Classes should work. Exposure to other languages like Java only seemed to further establish this idea. While these languages have their own semantics on how objects and classes work; Javascript, for the new user, is quite a revelation.

First off the bat, JavaScript objects are very different in the way they are created. There is no requirement for a class. Object instances can be created using the new operator:

let Reptile = new Object() {
 // ...
}

or with a function constructor

function Reptile() {
 // ...
}

Second, JavaScript objects are very flexible. While classic object oriented languages allow only property modification or property slots, JavaScript allows objects to modify their properties and methods; i.e. JavaScript objects have both property and method slots.

My first thought at the discovery was “yeah freedom!” but this came with a cost - a need to understand the prototype property in JavaScript. The knowledge of the prototype is essential to a developer who wishes to implement any semblance of an object oriented system in JavaScript.

All JavaScript objects are created from the Object constructor:

var Reptile = function(name, canItSwim) {
  this.name = name;
  this.canItSwim = canItSwim;
}

And the prototype allows us to add new methods to objects constructors, this means that the following method now exists in all instances of Reptile.

Reptile.prototype.doesItDrown = function() {
  if (this.canItSwim) {
    console.log(`${this.name} can swim`);
  } else {
    console.log(`${this.name} has drowned`);
  }
};

Object instances of Reptile can be now created:

// for this example consider alligators can swim and crocs cannot
let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); // alligator can swim

let croc = new Reptile("croc", false); 
croc.doesItDrown(); // croc has drowned

The prototype of the Reptile object is now the basis for inheritance, the doesItDrown method is accessible to both alligator and croc because the prototype of Reptile has this method. The prototype property is shared amongst all its instances and is accessible via the __proto__ property of a particular instance.

Now, because of the existence of method slots and a common prototype instance property being shared across all instances, some very neat tricks are possible which are very weird to C++ folks:

croc.__proto__.doesItDrown = function() {
  console.log(`the croc never drowns`);
};

croc.doesItDrown(); // the croc never drowns
alligator.doesItDrown(); // the croc never drowns

Change one instance’s prototype property or method, all instances of the object are affected. This means we could be deleting stuff as well. A croc tired of drowning could potentially do this:

delete croc.__proto__.doesItDrown
alligator.doesItDrown();

//TypeError: alligator.doesItDrown
// is not a function

Now no one gets to swim.

This is just a silly example to show how fundamental the prototype is to the Object system in JavaScript and how it can be quite jarring to people from other object oriented languages.

With the ES6 syntax, JavaScript has been provided the feature to create classes.

However, the concept of true classes does not exist in JavaScript but it is emulated through prototype and the class syntax is just syntactic sugar around it. Therefore, understanding this behavior is important to realize the convenience and limitations of ES6 classes.

With the new class syntax, Reptile would be defined as:

class Reptile {
  constructor (name, canItSwim) {
    this.name = name;
    this.canItSwim = canItSwim;
  }

  doesItDrown () {
   if(this.canItSwim) 
    console.log(`${this.name} can swim`);
   else
    console.log(`${this.name} has drowned`);
  }
}

let alligator = new Reptile("alligator", true);
alligator.doesItDrown(); //alligator can swim

This does not mean it brings nothing new to the offer for prototype users, some pitfalls can be avoided by using ES6 classes, like making the new keyword mandatory for creating instances.

let croc = Reptile("croc", false);
//TypeError: Class constructor Reptile cannot be invoked without 'new'

This is actually a good thing, since it prevents accessing the wrong context when using the object properties and methods, which is usually the global scope or the window object.

In Conclusion

Though JavaScript right now does certainly lack features like truly private members. It has made creating objects via class syntax, instead of prototypes closely resemble classes from other OO languages like C++/Java.


PS. There has been a proposal to TC39 for creating truly private members in JavaScript classes, you can follow it here and contribute your opinion. If it were to be included in the next revision, then we’d have something like:

class Foo {
  #a; #b; // # indicates private members here
  #sum = function() { return #a + #b; };
}

// personally this format reminds me of $variable in PHP.
// I'm not sure if that's a good thing 
  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#