Skip to content

Classes in JavaScript Explained – What Is a JavaScript Class?

A JavaScript class is an object constructor that the new keyword uses to create a new object instance.

Example of a JavaScript Class

// Define a JavaScript class:
class Name {}
// Create an object instance from the Name class:
const yourName = new Name();
// Check yourName's content:
yourName;
// The invocation above will return an empty object: { }

Try Editing It

The snippet above used the new keyword to create a new object instance from the class constructor.

Why Classes in JavaScript?

Classes provide a way to create a template for creating objects that have access to private data through public methods.

In other words, classes help you encapsulate your data while providing users indirect access to an instance’s internal workings. This helps you provide users with a clean and friendly interface that is independent of an object’s internal implementations.

For instance, Date is a JavaScript class that allows you to access its date data through its public methods, such as getDate(), setDate(), and getFullYear().

Syntax of a JavaScript Class

class NameOfClass {
// class's body
}

A class is composed of four components:

  1. A class keyword
  2. The class’s name
  3. A code block ({...})
  4. The class’s body

Types of JavaScript Classes

The three types of JavaScript classes are:

  • Class declaration
  • Class expression
  • Derived class

Let’s discuss each type.

What is a JavaScript class declaration?

A class declaration is a class created without assigning it to a variable.

Here’s an example:

class Numbers {}

The class above is a class declaration because we defined it without storing it in a variable.

What is a JavaScript class expression?

A class expression is a class you create and assign to a variable.

Here’s an example:

const myClassExpr = class Numbers {};

The class above is a named class expression that we assigned to the myClassExpr variable.

You can also write the snippet above as an anonymous class expression like so:

const myClassExpr = class {};

The class above is an anonymous function expression assigned to the myClassExpr variable.

Let’s now discuss derived classes.

What is a derived class in JavaScript?

A derived class is a class that extends the public and static features of an existing class.

In other words, a derived class is the child of a parent class.

Syntax of a derived class

We use the extends keyword to create a derived class.

Here’s the syntax:

class DerivedClass extends BaseClass {
// derived class's body
}

Once you extend a child class to a parent class, the derived class will inherit all its base class’s class fields.

Example: How to use a base class’s features in a derived class

// Create a new class:
class Name {
myName = "Oluwatobi";
}
// Create a derived class:
class Bio extends Name {}
// Create a new object instance:
const myBio = new Bio();
// Check myBio's current value:
myBio;
// The invocation above will return: { myName: "Oluwatobi" }

Try Editing It

The Bio class inherited its parent’s property because we used the extends keyword to assign the Name class as the derived class’s dunder proto.

Note: A derived class’s class field will override its parent class’s property with the same name. For example, consider the following code:

// Create a new class:
class Name {
myName = "Oluwatobi";
}
// Create a derived class:
class Bio extends Name {
myName = "Sofela";
}
// Create a new object instance:
const myBio = new Bio();
// Check myBio's current value:
myBio;
// The invocation above will return: { myName: "Sofela" }

Try Editing It

Now that we know the syntax and types of JavaScript classes let’s look at the main components in one piece.

Components of a JavaScript Class

The main features of a JavaScript class are as follows:

  1. A class keyword
  2. The class’s name
  3. The extends clause
  4. A code block ({...})
  5. The class’s body
  6. A constructor method
  7. super() function caller
  8. super property accessor
  9. Instance class fields
  10. Prototypal class fields
  11. Private class fields
  12. Static class fields
  13. Static initialization blocks

Let’s look at these features in a class declaration.

class ChildClass extends ParentClass {
constructor(parameter) {
super(parameter);
}
instanceClassField = "Value can be any valid JavaScript data type";
prototypalClassField() {
// prototypalClassField's body
}
#privateClassField = "Value can be any valid JavaScript data type";
static classField = "Value can be any valid JavaScript data type";
static classFieldWithSuperValue = super.parentProperty;
static #privateClassField = "Value can be any valid JavaScript data type";
static {
// Static initialization block's body
}
}

The constructor function equivalence of the snippet above looks like this:

function ChildClass() {
this.instanceClassField = "Value can be any valid JavaScript data type";
}
Object.setPrototypeOf(ChildClass, ParentClass);
ChildClass.prototype.prototypalClassField = function () {
// prototypalClassField's body
};
ChildClass.staticClassField = "Value can be any valid JavaScript data type";
ChildClass.staticClassFieldWithSuperValue =
Object.getPrototypeOf(ChildClass).parentProperty;
(function () {
// Static initialization block's body
})();

How Does a JavaScript Class Help with Encapsulation?

Classes let you prevent external code from interacting with internal class fields. Instead, external code would use public methods to operate on the class’s internal implementations.

For instance, consider the following code:

// Create a new class:
class Name {
// Create a private class field data:
#myName = "Oluwatobi";
// Create a publicly available method:
showMyName() {
return this.#myName;
}
// Create another publicly available method:
updateMyName(value) {
this.#myName = value;
}
}
// Create a new object instance:
const bio = new Name();
// Check the instance's data value:
bio.myName;
// The invocation above will return: undefined

Try Editing It

The snippet above encapsulated Name’s data because it defined myName as a private feature and provided two public methods for users to read and update the class’s internal implementation.

Consequently, the bio instance object knows nothing about the class’s internal data and cannot interact with it directly.

Whenever users need to access the encapsulated data, they would use the publicly available methods like so:

// Check the instance's data value:
bio.showMyName();
// The invocation above will return: "Oluwatobi"
// Update the instance's data value:
bio.updateMyName("Sofela");
// Check the instance's data value:
bio.showMyName();
// The invocation above will return: "Sofela"

Try Editing It

Encapsulating your data is an excellent way to keep your class clean. It prevents minor internal refactoring from breaking users’ code.

For instance, consider the following code:

// Create a new class:
class Name {
// Create a public class field data:
myName = "Oluwatobi";
}
// Create a new object instance:
const bio = new Name();
// Check the instance's data value:
bio.myName;
// The invocation above will return: "Oluwatobi"
// Update the instance's data value:
bio.myName = "Sofela";
// Check the instance's data value:
bio.myName;
// The invocation above will return: "Sofela"

Since the snippet above did not encapsulate the class’s data, refactoring the class field’s name would break users’ code.

Here’s an example:

class Name {
// Update the data's name from myName to myFirstName:
myFirstName = "Oluwatobi";
}
// Create a new object instance:
const bio = new Name();
// Check the instance's data value:
bio.myName;
// The invocation above will return: undefined

The snippet above returned undefined because refactoring the class’s internal implementation broke the user’s bio.myName code. For the application to work appropriately, the user must update every instance of the code (which can be burdensome for large projects).

However, encapsulation prevents such refactoring from breaking the user’s code.

Here’s an example:

class Name {
// Update the data's name from myName to myFirstName:
#myFirstName = "Oluwatobi";
// Create a publicly available method:
showMyName() {
return this.#myFirstName;
}
// Create another publicly available method:
updateMyName(value) {
this.#myFirstName = value;
}
}
// Create a new object instance:
const bio = new Name();
// Check the instance's data value:
bio.showMyName();
// The invocation above will return: "Oluwatobi"
// Update the instance's data value:
bio.updateMyName("Sofela");
// Check the instance's data value:
bio.showMyName();
// The invocation above will return: "Sofela"

You can see that refactoring the class’s internal implementation did not break the user’s code. That’s the beauty of encapsulation!

Encapsulation allows you to provide users with an interface independent of the class’s underlying data. Therefore, you minimize the likelihood of users’ code breaking when you alter internal implementations.

Important Stuff to Know about JavaScript Classes

Here are five essential facts to remember when using JavaScript classes.

1. Declare your class before you access it

Classes are like constructor functions but have the same temporal dead zone behavior as const and let variables.

In other words, JavaScript does not hoist class declarations. Therefore, you must first declare your class before you can access it. Otherwise, the computer will throw an Uncaught ReferenceError.

Here’s an example:

// Create an object instance from the Name class:
const name = new Name();
// Define the Name class:
class Name {}

Try Editing It

The snippet above throws an Uncaught ReferenceError because JavaScript does not hoist classes. So, invoking Name() before its definition is invalid.

2. Classes are functions

The typeof a class is a function because, under the hood, the class keyword creates a new function.

For instance, consider the following code:

// Define a JavaScript class:
class Bio {
// Define two instance class fields:
firstName = "Oluwatobi";
lastName = "Sofela";
// Create a prototypal method:
showBio() {
return `${firstName} ${lastName} runs CodeSweetly.`;
}
}
// Create a new object instance:
const aboutMe = new Bio();
// Check what data type the Bio class is:
typeof Bio;
// The invocation above will return: "function"

Try Editing It

The computer processes the snippet above like so:

  1. Create a new function named Bio.
  2. Add the class’s instance properties to the newly created function’s this keyword.
  3. Add the class’s prototypal properties to the newly created function’s prototype property.

3. Classes are strict

JavaScript executes classes in strict mode. So, follow the strict syntax rules when you use classes. Otherwise, your code will throw errors—some of which will be silent errors that are difficult to debug.

4. Avoid the return keyword in your class’s constructor method

Suppose your class’s constructor returns a non-primitive value. In that case, JavaScript will ignore the values of all the this keywords and assign the non-primitive to the new keyword expression.

In other words, a constructor’s returned object overrides its keyword this.

For instance, consider the following code:

// Create a new class:
class Name {
constructor() {
this.firstName = "Oluwatobi";
this.lastName = "Sofela";
return { companyName: "CodeSweetly" };
}
}
// Create a new object instance:
const myName = new Name();
// Check myName's current value:
myName;
// The invocation above will return: { companyName: "CodeSweetly" }
// Check firstName's current value:
myName.firstName;
// The invocation above will return: undefined
// Check lastName's current value:
myName.lastName;
// The invocation above will return: undefined

Try Editing It

The new keyword expression returned only { companyName: "CodeSweetly" } because JavaScript ignores the constructor method’s this keywords whenever you use a return operator to produce an object.

5. A class’s evaluation starts from the extends clause to its values

JavaScript evaluates your class according to the following order:

1. extends clause

If you declare an extends clause, the computer will first evaluate it.

2. Extract the class’s constructor

JavaScript extracts the class’s constructor.

3. Parse the class’s property names

The computer analyzes the class’s class field names (not their values) according to their order of declaration.

4. Parse the class’s methods and property accessors

JavaScript analyzes the class’s methods and property accessors according to their order of declaration by doing the following:

  • Add the prototypal methods and property accessors to the class’s prototype property.
  • Analyze the static methods and property accessors as the class’s own properties, which you can call on the class itself.
  • Analyze the private instance methods and property accessors as private properties of the class’s instance object.

5. Parse the class’s property values

The computer analyzes the class’s class field values according to their order of declaration by doing the following:

  • Save each instance field’s initializer expression for later evaluations. JavaScript will evaluate the initializer expression during the following periods:
    • When the new keyword is creating an instance object.
    • While processing the parent class’s constructor.
    • Before the super() function call returns.
  • Set each static field’s keyword this to the class itself and create the static property on the class.
  • Evaluate the class’s static initialization blocks and set their keyword this to the class itself.