ES6 introduces the class
keyword. At first glance this looks like a completely new object model that follows classical languages like Java or Ruby, but behind the new syntax there is nothing new. The class
syntax just provides an alternative way to create plain old JavaScript objects.
In this post I want to explain ES6 classes in the context of the JavaScript objects they produce.
Proto refresher
Let’s start with a little refresher of prototype chains in JavaScript.
Every object in JavaScript has a special related object called the prototype. Prototypes are simple ways to share behaviour and data between multiple objects.
In code a prototype looks like this:
```js vehicle.proto = machine // machine is the prototype of vehicle
car.proto = vehicle // vehicle is the prototype of car ```
This is a prototype chain:
js
car -> vehicle -> machine
When JavaScript looks for a property that doesn’t exist in a particular object (e.g. car), it will attempt to look for that property in each object on the prototype chain (e.g. first in vehicle, then in machine). It will walk along the chain until it finds the attribute or return undefined
if it can’t be found.
If you’d like to get a better understanding of this, please see this post.
ES6
In JavaScript we have Function Constructors, which have been the common way to build new objects until ES6. Unfortunately Function Constructors can be quite confusing to understand (especially if you want to model inheritance). To alleviate this, ES6 introduces the class
syntax.
Classes in ES6 don’t add any functionality to what we already have in the language, they are just a simpler syntax for building the same objects as we had before.
ES6 Classes
Take an ES6 class like this:
```js class Car { constructor(name) { this.kind = ‘Car’; this.name = name; }
printName() {
console.log('this.name');
} }
```
Let’s create a car:
js
var mazda = new Car('Mazda');
This creates a JavaScript object. This object gets a prototype automatically assigned to it, more on this in a bit.
First let’s explore the attributes assigned in the constructor
.
js
mazda.hasOwnProperty('kind') // true
mazda.hasOwnProperty('name') // true
kind
and name
are properties directly assigned on the new mazda
object. If you were to represent this object in an object literal it would look something like:
js
var mazda = {
kind: 'Car',
name: 'Mazda'
}
What about printName
, where is this?
js
mazda.hasOwnProperty('printName') // false
It is not in the object itself, but we can call it:
js
mazda.printName() //Mazda
So we can deduce that it is somewhere in the prototype chain of mazda. Let’s try the prototype of mazda:
js
mazda.__proto__.hasOwnProperty('printName') // true
This method was not copied when creating mazda, it is in the prototype.
Where did this prototype object came from?
This object is created automatically by JavaScript when declaring a class and assigned to all the instances of the class.
Let’s create two cars now:
js
var mazda = new Car('Mazda');
var bmw = new Car('BMW');
If we compare the prototypes of mazda
and bmw
we find that they are the same object:
js
mazda.__proto__ == bmw.__proto__ //-> true
Thus the method printName
is being shared by all instances of Car. Adding methods on the prototypes of objects in JavaScript is an efficient way to conserve memory (as opposed to copying the methods on each object).
Prototypes are just objects that can be changed at runtime
Let’s change the method printName
after creating an instance:
```js var mazda = new Car(‘Mazda’); var bmw = new Car(‘BMW’);
mazda.proto.printName = function () { return ‘Oops’ } bmw.printName() //-> Oops ```
Changing the method on the prototype of mazda
changes the result on bmw
as well. This is because they share the same prototype and we are changing that object.
What if we create yet another Car after doing this?
js
var honda = new Car('Honda');
honda.printName() // Oops
Oops, there is no going back, we have already modified the prototype for all instances of Car, now and in the future.
Remember, classes in JavaScript are not a blueprint like in other languages. They just define objects that can be modified at will in runtime.
To recap:
```js class Car { constructor(name) { // - this is the newly created object // - anything assigned here will be assigned // to the object directly this.kind = ‘Car’; this.name = name; }
// - class methods are assigned to the prototype of
// the newly created object
// - this prototype object will be shared by all instances
printName() {
console.log('this.name');
} }
```
Inheritance
ES6 classes make inheritance much easier to model and understand than before:
```js class Vehicle { constructor(name) { this.kind = ‘Vehicle’; this.name = name; }
printName() {
console.log(this.name);
} }
class Car extends Vehicle { constructor(name) { super(name); //call the parent method with super this.kind = ‘Car’; }
} ```
Let’s make some vehicles:
js
var boat = new Vehicle('Boat');
var mazda = new Car('Mazda');
What is the relationship between these two?
js
boat.__proto__ == mazda.__proto__ // false
Ok, boat and mazda don’t share a prototype. But we can call printName
on both:
js
boat.printName() //-> Boat
mazda.printName() //-> Mazda
So, there’s something shared here. What is happening is that the keyword extends
is instructing JavaScript to create a prototype chain for us:
js
mazda.__proto__.__proto__ == boat.__proto__ //-> true
Let’s double check by modifying this prototype:
js
mazda.__proto__.__proto__.printName = function () { console.log('Ha') }
boat.printName() // Ha
Conclusion
ES6 classes just create the same prototype chains we know and love from previous versions of JavaScript, but with a much saner syntax than Function Constructors.
I hope this post helps to clarify how classes work in ES6 and how they relate to objects in JavaScript.