Classes and prototypes are the same

We now know how to make objects with shared properties and behavior with constructor functions and know how they work through the prototype chain.

Now we're going to move onto a relatively new addition to the language that came in ES2015, classes.

And right now at the outset of learning classes, you have the proper foundation to understand them. Many JavaScript developers do not. Why? Because they don't understand the prototype chain and how it works.

And why does that matter? Because classes in JavaScript and prototypes aren’t different.

Remember this: Classes are just a cleaner way work with the constructor functions and the prototype.

This is proven by many JavaScript libraries and frameworks such as React and Angular that have adopted classes and have made full use of them to help you make better applications. And we’re going to see how to fully use classes ourselves to help us make great applications with just plain JavaScript.

Classes in JS vs other languages

If you're familiar with other languages, the word classes may be misleading. In other languages, for example Ruby, when you create a new instance of a class, you copy all the properties and methods onto the new object.

However, JavaScript, considering its based on prototypical inheritance instead of classical inheritance, when you create a new instance, methods and properties are not copied to that new object. You’re merely linking that created object to the function constructor's prototype, which is the source of all of its inherited properties. When you call a method on an instance of a constructor, you’re calling it from that function's prototype, which is itself an object instance, and we understand through the prototype chain.

So whenever you see a class in JavaScript, think in your mind: prototype

// class -> prototype

Our first class

Let's create our first class. To declare a class, we use the class keyword in front of it. It uses the same naming convention as constructor functions, where the name of the class should be capitalized. And like a constructor function, it has a body of curly braces, but unlike functions, it doesn't have parentheses for parameters.

// function Student() {}
// class Student {}

Why am I comparing a class to a function, aren't they different? In fact, they aren't. And to make it clear that they are merely functions, we can check the type of our created class.

typeof class Student {}; // 'function'

Another thing you should remember about classes: Every class is just a function, namely a constructor function. And the purpose of classes is the same, to create objects with certain properties and methods.

Declaring properties

But if classes are ultimately constructor functions, how do we pass them data to make our objects if we don't have parameters?

The first thing to note about classes is that instead of having to declare methods on the prototype property (which results in a lot repetition in the end), we can write methods directly within the class body and we can use the same syntax as we do for objects. For example, take our addSubject method from before:

class Student {
  addSubject() {}
}

And for creating properties, we use a special method called something very familiar--the constructor. This method should always be the first one you declare in your class, if you need to create any properties. Part of the job of the constructor is creating a this context. You aren’t required to declare a constructor function, but it’s where you will declare your properties, so you’ll write one in most cases. And try to guess how it is written?

Exactly like our constructor functions from before, but always with the method name as 'constructor':

class Student {
  constructor() {}

  // addSubject() {

  // }
}

This is where our class accepts any arguments that we pass to it. So let's convert our previous Student constructor function to a class. It really just amounts to copying it over as a method and naming it constructor:

class Student {
  constructor(id, name, subjects = []) {
    this.id = id;
    this.name = name;
    this.subjects = subjects;
  }

  addSubject() {
    console.log("adding subject");
  }
}

The constructor method runs first when the class is instantiated and the created object is returned as before.

We instantiate objects with classes just as we did with constructor functions, with the new keywords and passing arguments in to serve as our properties:

const student3 = new Student(3, "Bob");

Improvements / Common errors

Note that instantiating classes comes with some improvements over constructor functions. One being that we will get an error if we don't use the new keyword when calling it, so we can be sure it will work as intended.

Student(3, "Bob"); // Uncaught TypeError: Class constructor Student cannot be invoked without 'new'

Also note that with our Student class, there are no commas after methods. A common mistake for those first using classes is to add commas after each method, as if we were working with an object literal. Class methods are not properties, unlike object methods, and we can confirm this with any method, if we try to access it like we would an object method.

Student.addSubject; // undefined

Think for a second back to what we covered about constructor functions and the prototype chain. Where would you expect any method declared on our class to be?

Correct, on the prototype:

Student.prototype; // {constructor: ƒ, addSubject: ƒ}

so make sure to not to add commas after them, otherwise you'll get a syntax error:

class Student {
  constructor(id, name, subjects = []) {
    this.id = id;
    this.name = name;
    this.subjects = subjects;
  },

  addSubject() {
    console.log("adding subject");
  },
}
// Uncaught SyntaxError: Unexpected token ','

Everything is public

Once we have an instance of our class, an object in other words, note that you can access anything off of it using the familiar dot syntax or array syntax, both the properties made in the constructor as well as any methods we declared in the body:

student3.id; // 3
student3["name"]; // 'Bob'
student.addSubject(); // 'adding subject'

In other languages you may have the ability for classes to determine whether data on it as public or private. Currently, everything is public.

Speaking of context. You have full access to the this context in the methods if you call them directly on an instance of a class. This will work as predicted most of the time. You’ll see the exceptions in upcoming tips.

This means that you can create methods that refer to properties or other methods. Let's say we wanted to have a function that gets the current student's name:

class Student {
  constructor(id, name, subjects = []) {
    this.id = id;
    this.name = name;
    this.subjects = subjects;
  }

  getStudentName() {
    return `Student: ${this.name}`;
  }

  addSubject() {
    console.log("adding subject");
  }
}
const​ student3 =newStudent(3, 'Bob');
​student3.getStudentName(); // 'Student: Bob';

Advantages vs. disadvantages of classes

Let's do a quick overview of the pluses and negatives of classes. I recommend using classes for the following reasons:

  • Classes make constructor functions and prototypical inheritance, that long word that just means add stuff to the prototype, a lot easier. We add properties in the constructor method and methods directly on the class body. No more having to access the prototype property every time we want to make a method.
  • Classes are a common standard for object creation and inheritance that is now widely supported across frameworks (React, Angular, Ember, etc.). This is an improvement to how things were before, when almost every framework had its own inheritance library.
  • JavaScript engines optimize them. That is, code that uses classes is almost always faster than code that uses a custom inheritance library.

That doesn’t mean that classes are perfect:

  • How they work superficially and under the hood is quite different. In other words, there is a disconnect between syntax and semantics. Two examples are:
  • A method definition inside a class C creates a method in the object C.prototype.
  • Classes are functions.