Classes in JavaScript are a special syntax for its prototypical inheritance model that is a comparable inheritance in class-based object oriented languages. Classes are just special functions added to ES6 that are meant to mimic the class
keyword from these other languages. In JavaScript, we can have class
declarations and class
expressions, because they are just functions. So like all other functions, there are function declarations and function expressions.
Classes serve as templates to create new objects.
The most important thing to remember: Classes are just normal JavaScript functions and could be completely replicated without using the class
syntax. It is special syntactic sugar added in ES6 to make it easier to declare and inherit complex objects.
Defining Classes
To declare a class, we use the class
keyword. For example, to declare a simple class, we can write:
class Person{
constructor(firstName, lastName) {
this.firstName= firstName;
this.lastName = lastName;
}
}
Class declarations aren’t hoisted so they can’t be used before they are defined in the code, as the JavaScript interpreter will not automatically pull them up to the top. So the class above won’t work before it’s defined in the code like the following:
const person = new Person('John', 'Smith');class Person{
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
We will get a ReferenceError
if we run the code above.
We can also define a class by a class expression, which is an alternative syntax. They can be named or unnamed. We can also assign a class to a variable like we do with functions. If we do that, we can reference the class by its name. For example, we can define:
let Person = class {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
To get the name of the unnamed classes above, we can get the name with the name
property, like so:
console.log(Person.name);
We can also define a named class like the following:
let Person = class Person2 {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Then to get the name of the class, we can use the name
property again. So we if we write:
console.log(Person.name)
We get Person2
logged.
The class body is defined with curly brackets. We define the class members inside the brackets. The body of the class is executed in strict mode, so everything defined in strict mode applies to the definition of a class, so we can’t define variables without keyword before it like var
, let
or const
, and many other rules apply when you define a class.
Classes in JavaScript also have a constructor
method that lets us set fields when the object is instantiated with a class
. Each class can only have one constructor
method in it. If there’s more than one, then SyntaxError
will be thrown. A constructor
can also call the super
method to call the constructor
of the super class if the class extends a parent class.
Methods that aren’t declared static
constitutes the prototypical methods of the class. They are called after an object has been created by using the new
keyword. For example, the following class has only prototypical methods:
class Person{
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
} get fullName(){
return `${this.firstName} ${this.lastName}`
} sayHi(){
return `Hi, ${this.firstName} ${this.lastName}`
}
}
In the Person
class above, fullName
and sayHi
are prototypical methods. They are called like this:
const person = new Person('Jane', 'Smith');
person.fullName() // 'Jane Smith'
Static methods are methods that can be called without creating an object from the class using the new
keyword. For instance, we can have something like the following:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`
}
sayHi() {
return `Hi, ${this.firstName} ${this.lastName}`
}
static personCount() {
return 3;
}
}
We can call the personCount
function without using the new
keyword to create an instance of the class. So if we write:
Person.personCount
We get 3 returned.
The this
value inside prototypical methods will be the value of the object. For static methods the value of this
has the class that the static method is in as the value.
<img alt="Image for post" class="s t u dc ai" src="https://miro.medium.com/max/11426/0BvO6QcBmjOGtcHnL" width="5713" height="3809" srcSet="https://miro.medium.com/max/552/0BvO6QcBmjOGtcHnL 276w, https://miro.medium.com/max/1104/0BvO6QcBmjOGtcHnL 552w, https://miro.medium.com/max/1280/0BvO6QcBmjOGtcHnL 640w, https://miro.medium.com/max/1400/0*BvO6QcBmjOGtcHnL 700w" sizes="700px"/>
Photo by Thomas Kelley on Unsplash
Getters and Setters
JavaScript classes can have getter and setter functions. Getters, as the name suggests, is a method that lets us get some data from a class. Setters are methods that gives us the ability to set some fields of the class. We denote getter functions with the get
keyword and setters with the set
keyword. For example, we can write a class that has getters and setters like the following:
class Person {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`
}
get firstName() {
return this._firstName
}
get lastName() {
return this._lastName
}
sayHi() {
return `Hi, ${this.firstName} ${this.lastName}`
}
set firstName(firstName) {
this._firstName = firstName;
}
set lastName(lastName) {
this._lastName = lastName;
}
}
Then when we use the new
keyword to construct a Person
object, we can use them in the following way:
const person = new Person('Jane', 'Smith');
person.firstName = 'John';
person.lastName = 'Doe';
console.log(person.firstName, person.lastName)
Since we have the getter and setter functions, we can use them to set the data directly to set the data for firstName
and lastName
of the Person
class. In the setter functions, which start with the keyword set
, when we assign to a value them, it gest passed into the parameters and set in the member of the class. In the getter functions, which are denoted by get
we return the member values which triggers the associated get
function for the value.
JavaScript Inheritance
In JavaScript, we can create classes where the properties can be included in the properties of a child class.
So, we can have a high-level class that contains the properties that are common to all the child classes, and the child class can have its own special properties that are not in any other classes.
For example, if we have an Animal
class with the common properties and methods, like name
and the eat
method, then the Bird
class can just inherit the common properties in the Animal
class. They don’t have to be defined in the Bird
class again.
We can write the following to do inheritance in JavaScript:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log('eat');
}
}class Bird extends Animal {
constructor(name, numWings) {
super(name);
this.numWings = numWings;
}
}const bird = new Bird('Joe', 2);
console.log(bird.name)
bird.eat();
In the example above, we have the parent class, Animal
, that has the eat
method, which all classes that extends
from Animal
will have, so they don’t have to define eat
again.
We have the Bird
class which extends the Animal
class. Note that in the constructor
of the Bird
class, we have the super()
function call to call the parent’s class constructor to populate the properties of the parent class in addition to the properties of the child class.
Classes cannot extend regular objects, which cannot be constructed with the new
keyword. If we want to inherit from a regular object, we have to use the Object.setPrototypeOf
function to set a class to inherit from a regular object. For example:
const Animal = {
eat() {
console.log(`${this.name} eats`);
}
};class Cat{
constructor(name) {
this.name = name;
}
}class Chicken{
constructor(name) {
this.name = name;
}
}Object.setPrototypeOf(Cat.prototype, Animal);
Object.setPrototypeOf(Chicken.prototype, Animal);let cat = new Cat('Bob');
let chicken = new Chicken('Joe');
cat.eat();
chicken.eat();
If we run the example code above, we can see Bob eats
and Joe eats
logged because we have inherited the eat
function from the Animal
object.
this
Keyword
The this
keyword allows us to access the current object’s properties inside the object, unless you’re using arrow functions.
As we can see from the above example, we can get the properties of the instance of the child and the parent class in the object.
Mixins
We can use mixins to do multiple inheritance in JavaScript. Mixins are templates for creating classes. We need mixins to do multiple inheritance because JavaScript classes can only inherit from one super class, so multiple inheritance isn’t possible.
For example, if we have a base class, we can define mixins to incorporate the members from multiple classes into one by composing the mixins by calling one and then pass the returned result into the next one as the argument, and so on, like the following:
class Base {
baseFn() {
console.log('baseFn called');
}
}let classAMixin = Base => class extends Base {
a() {
console.log('classAMixin called');
}
};let classBMixin = Base => class extends Base {
b() {
console.log('classBMixin called');
}
};class Bar extends classAMixin(classBMixin(Base)) {}
const bar = new Bar();
bar.baseFn()
bar.a()
bar.b()
In the code above, we have the Base
class which we pass into the classBMixin
to get the b
function into the Base
class, then we call the classAMixin
by passing in the result of classBMixin(Base)
into the argument of the classAMixin
to return the a
function from classAMixin
into the Base
class and then return the whole class with all the functions from all the classes incorporated into one.
If we call all the functions above like we did by creating an instance of the Bar
object and then call the baseFn
, a
, and b
functions, we get:
baseFn called
classAMixin called
classBMixin called
This means that we have all the functions from the mixins incorporated into the new Bar
class.
In JavaScript, classes are just syntactic sugar to make the prototypical inheritance of JavaScript clearer by letting us structure the code in a way that’s more like typical inheritance in class-based object oriented inheritance pattern. This means that we write classes to use the new
keyword to create objects from the classes, but underneath the syntactic sugar, we are still using prototypical inheritance to extend objects. We can extend classes from objects and we can also use mixins to do multiple inheritance in JavaScript classes.