JavaScript Classes - Basics

on
  • JavaScript

In ES6 (or ES2015 if you will), one great new addition is the new class syntax, known from almost every other classical object-oriented programming language like Java, C++, etc.

In this post, we're going from the plain JavaScript functions to the new ES6 class definitions.

While JavaScript is object-oriented it uses the so called prototypical inheritance instead of the more common classical inheritance approach. What this basically means is, objects are constructed from other objects instead of declarative object definitions. JavaScript uses functions as constructors and if called with the keyword new they will create a new object using the functions prototype property as their parent.

Example in plain old JavaScript

function Foo() {
  console.log('Constructing new foo...')
}

Foo.prototype.hello = function() {
  console.log('Hello World')
}

With this new Foo function, we can instantiate a new object with a hello method.

var foo = new Foo() // Logs 'Constructing new foo...'

foo.hello() // Logs 'Hello World'

By default, the Foo.prototype is a plain JavaScript object, with a constructor property which points to Foo and inherits from the global Object object. You can lookup and manually set the inherited object by accessing / setting the __proto__ property on any object. This was non-standard up until ES6.

Foo.prototype.constructor === Foo // true
Foo.prototype.__proto__ === Object.prototype // true

I don't recommend using the __proto__ accessor as it is only standardized because most browser started implementing it and there were use cases where no other solution was available. There are also Object.getPrototypeOf(obj) and Object.setPrototypeOf(obj, proto), but I highly recommend to never use Object.setPrototypeOf() or to set the __proto__ property.

Extending JavaScript objects

To inherit from a object, all you need to do is setting the prototype property of your object to an instance of your super class.

Example:

function Bar() {

}

Bar.prototype = new Foo()

Bar now has all the methods of our Foo class. But there is one little problem here, if you noticed, we constructed an instance our Foo class to be set on Bars prototype, this can cause serious problems as the constructor Foo might have side-effects or do other stuff we don't want yet. In our example above, the code even logged 'Constructing new foo...'. In ES5, there is a new function, Object.create(obj), which will help us to prevent this. Object.create() creates a new object which directly inherits from the given object.

Example:

function Bar() {

}

Bar.prototype = Object.create(Foo.prototype)

We are now setting the Bar.prototype to a new instance of Foo.prototype, this way, Bar inherits all the methods of Foo and no other methods or constructors will be called. Don't forget to call the parent constructor (Foo) in your new constructor (Bar) on your currently constructing object. Unfortunately this approach also kills of our initial constructor property on our Bar.prototype, you might want to reassign it afterwards.

function Bar() {
  Foo.apply(this, arguments)
}

Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.constructor = Bar

Now we have correctly inherited Bar instances from Foo. But our if our Foo class has some static methods, we won't get these inherited in this example. By setting the Bar.__proto__ to Foo we even get the static members correctly inherited.

Full example:

function Foo() {

}

Foo.staticHello = function() {
  return 'Hello static World'
}

Foo.prototype.hello = function() {
  return 'Hello World'
}

function Bar() {
  Foo.apply(this, arguments)
}

Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.constructor = Bar
Bar.__proto__ = Foo

var f = new Foo
var b = new Bar

console.log(f.hello()) // Hello World
console.log(b.hello()) // Hello World
console.log(Foo.staticHello()) // Hello static World
console.log(Bar.staticHello()) // Hello static World

Class Syntax

As you might have noticed, this is all very verbose and quite a bit weird. Because lots of people have issues and lots of libraries implemented their own class / object inheritance system, ES6 now comes with a native syntax for achieving exactly what we did in our last example.

To achieve the same as in my last example we can simply write this:

class Foo {
  static staticHello() {
    return 'Hello static World'
  }

  hello() {
    return 'Hello World'
  }
}

class Bar extends Foo {

}

var f = new Foo
var b = new Bar

console.log(f.hello()) // Hello World
console.log(b.hello()) // Hello World
console.log(Foo.staticHello()) // Hello static World
console.log(Bar.staticHello()) // Hello static World

Voilà, no more using weird underscored accessors, no more manually calling the super constructor, everything is done for you. The constructor in Bar is automatically derived if none is defined. The constructor and class methods may now even call super methods.

Example:

class Bar extends Foo {
  constructor() {
    // super must be called before any use of `this`
    // because it is the parents job to construct the initial object
    super()
  }

  hello() {
    return super.hello()
  }
}

One small note, there is no way yet to define instance variables, you have to set them in the constructor for now.

This was a short introduction to ES6 classes, there is a lot more going on here. For a more in-depth explanation I recommend reading Dr. Axel Rauschmayer article on Classes in ECMAScript 6

Happy extending.