JavaScript Classes - Basics
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 Bar
s 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.