This page is a work in progress.You can help improve it. →

JavaScript classes

JavaScript as an Object Prototype language

Before 2015, JavaScript was an object-based language that did not have the concept of classes. What JavaScript did have was the idea of a prototype. An object's prototype was the collection of methods that the object could do.

You could define a prototype like this:

function Dog() {
this.bark = function () {
return 'bark'
}
this.eat = function () {
return 'mmmmmm'
}
}

This is a function named Dog that returns a new object which contains keys of the names of the methods we can execute and values that are the function definitions themselves.

We could create new instances of Dog as such:

const riley = new Dog()
const roxy = new Dog()
const rover = new Dog()

And we could call methods on these objects like:

riley.bark()
roxy.eat()
rover.bark()

However, defining instance data on these objects wasn't always easy and the format for defining object prototypes was very unfamiliar to developers coming from class-based object-oriented languages like Java, C#, Ruby, and others.

Enter class

In 2015, the class syntax was added to JavaScript and we could now use a more familiar syntax for defining new objects. Realistically the implementation is the same, this new syntax is a form of syntactic sugar. That is, a new syntax that exists only to make an existing feature easier to use.

Let's redefine our Dog

class Dog {
bark() {
return 'bark'
}
eat() {
return 'mmmm'
}
}

Ahhhhh, much better. A new object created from this via const buster = new Dog() will act the same way as our previous prototypes would.

Public field declarations

Programmers of languages such as C# might recognize these as property definitions.

Within a class declaration, we can identify new properties of objects created from this class. For instance, we can give these dogs a name property by:

class Dog {
name = 'Not Named'
bark() {
return `${this.name} says bark!`
}
eat() {
return 'mmmm'
}
}

Notice we can use this within a method to refer to the current object and access it's name property.

The name property is available outside of the class as well:

const newDog = new Dog()
newDog.bark() // Not Named says bark!
newDog.name // Not Named
newDog.name = 'Fluffy'
newDog.bark() // Fluffy says bark!
newDog.name // Fluffy

Constructors

We can even apply default values in the constructor. The constructor is the method called when we say new Dog() -- we get a default constructor for free. That is we haven't had to declare one yet!

class Dog {
name = 'Not Named'
constructor(newName) {
this.name = newName
}
bark() {
return `${this.name} says bark!`
}
eat() {
return 'mmmm'
}
}

Now when we create a new dog we must give it a proper name.

const myPal = new Dog('Fluffy')
myPal.bark() // Fluffy says bark!
myPal.name // Fluffy

Subclasses

Let's say we have specific kinds of dogs that bark very loudly. We could define a subclass of Dog that has a unique bark method.

class LoudDog extends Dog {
bark() {
return `${this.name.toUpperCase()} SAYS BARK!!!!!`
}
yell() {
return 'I am a loud dog, so I yell!'
}
}

Now when creating this kind of Dog all the functions and properties of Dog are available as well as this changed bark method and a new yell method.

const jack = new LoudDog('Jack')
jack.bark()

Constructors in subclasses and super

In a subclass, we may also implement a constructor. If so we may want to call the parent class constructor as well. We can do that via the super method.

class LoudDog extends Dog {
constructor(name) {
super(name.toUpperCase())
}
bark() {
return `${this.name} SAYS BARK!!!!!`
}
yell() {
return 'I am a loud dog, so I yell!'
}
}

Now if we create a new LoudDog the name property will be all uppercase since we are passing, to Dog, an uppercase dog name.

const barkeyMcBarkson = new LoudDog('Barkey McBarkson')
barkeyMcBarkson.name // 'BARKEY MCBARKSON'

Arrow function methods

There is another way to define methods for a class, to use the public field definition syntax.

Let's add a greet method of that style.

class Dog {
name = 'Not Named'
constructor(newName) {
this.name = newName
}
greet = () => {
return `Hello I am ${this.name}`
}
bark() {
return `${this.name} says bark!`
}
eat() {
return 'mmmm'
}
}

This greet method works exactly like the other methods of the class, except for one small difference we don't yet see. That is, the value of this is handled slightly differently. In the case of the arrow function, the this value is always the instance of the object itself. However, for our normally defined methods like bark and eat it isn't the case. The subtle difference here won't make a big impact on us until we get to dealing with things like event callback functions in React so we'll hold off any further discussion of the concept of this.

If you are very curious about the subject of this, an often discussed and confusing feature of the language, you can read this excellent MDN article on the matter.

© 2017 - 2022; Built with ♥ in St. Petersburg, Florida.