Introduction
Classes are a template for creating objects. They encapsulate data with code to operate on that data. Classes in JS are built on prototypes, but they also have some syntax and semantics that are unique to classes.
Classes are essentially “special functions,” and just as you can define function expressions and function declarations, a class can be defined in two ways: a class expression or a class declaration.
// Declaration
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
// Expression; the class is anonymous but assigned to a variable
const Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// Expression; the class has its own name
const Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};Like function declarations, class declarations may be anonymous or have a different name than the variable they are assigned to. However, unlike function declarations, class declarations have the same dead zone temporal restrictions as let or const and behave as if they were not lifted.
Class body
The body of a class is the part enclosed in curly braces {}. This is where you define the members of the class, such as methods or constructors.
The body of a class is executed in strict mode, even without the "use strict" directive.
A class element can be characterized by three aspects:
- Type: Receiver, Regulator, Method or Field
- Location: Static or Sample
- Visibility: Public or Private
Constructor
A constructor method is a special method for creating and initializing an object created with a class. There can only be one special method named “constructor” in a class – if the class contains more than one instance of a constructor method, a SyntaxError will be thrown.
A constructor can use the super keyword to call the super class constructor.
You can create instance properties inside the constructor:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}Alternatively, if the values of your instance properties do not depend on the constructor arguments, you can define them as class fields.
Static initialization blocks
Static blocks allow flexible initialization of static properties, including evaluating expressions during initialization, while still allowing access to private scope.
Multiple static blocks can be declared, and these blocks can be interspersed with declarations of static fields and methods (all static items are evaluated in the order of declaration).
Methods
Methods are defined on the prototype of each instance of a class and are shared by all instances. Methods can be simple functions, asynchronous functions, generator functions, or asynchronous generator functions. For more information, see Method Definitions.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
*getSides() {
yield this.height;
yield this.width;
yield this.height;
yield this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]Static methods and fields
The static keyword defines a static method or field for a class. Static properties (fields and methods) are defined on the class itself, rather than on each instance. Static methods are often used to create utility functions for an application, while static fields are useful for caching, fixed configuration, or any other data that does not need to be repeated across instances.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined
console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755Field announcements
With the class field declaration syntax, the constructor example can be written as follows:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}Class fields are like object properties, not variables, so we don't use keywords like const to declare them. In JavaScript, private properties use a special identifier syntax, so modifier keywords like public and private shouldn't be used either.
As seen above, fields can be declared with or without default values. Fields without default values are not defined by default. By declaring fields up front, class definitions are more documented and the fields are always present, which helps with optimization.
See Public Class Fields for more information.
Private properties
Using private fields, the definition can be modified as follows.
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}It is an error to refer to private fields from outside the class. They can only be read or written from within the class body. By defining things that are not visible outside the class, you ensure that users of your class cannot depend on internals, which may change from version to version.
Private fields can only be declared in advance in a field declaration. They cannot be created later by assignment to them, the way regular properties can.
See Private Property for more information.
Inheritance
The extends keyword is used in class declarations or class expressions to create a class as a child of another constructor (class or function).
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.If there is a constructor in the subclass, it must first call super() before using it. The super keyword can also be used to call methods related to the superclass.
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.Evaluation order
When a class declaration or class expression is evaluated, its various components are evaluated in the following order:
- The expanded clause, if any, is evaluated first. It must evaluate to a valid constructor function or null, otherwise a TypeError is thrown.
- The constructor method is extracted and if the constructor is not present, it is replaced with the default implementation. However, since the constructor definition is just a method definition, this step is not visible.
- Property keys of class elements are evaluated in the order they are declared. If the property key is evaluated, the expression evaluated with this value is set to this value that surrounds the class (not the class itself). None of the property values have been evaluated yet.
- Methods and accessors are installed in the order they are declared. Instance methods and accessors are installed on the prototype property of the current class, and static methods and accessors are installed on the class itself. Private instance methods and accessors are reserved to be installed directly on the instance later. This step is not visible.
- The class is now initialized with the prototype specified by extensions and the implementation specified by the constructor. For all of the above steps, if an evaluated expression tries to access the class name, a ReferenceError is thrown because the class has not yet been initialized.
- The values of class elements are evaluated in the order of declaration:
- For each instance field (public or private), its initializer expression is stored. The initializer is evaluated when the instance is created, at the start of the constructor (for base classes), or immediately before the super() call returns (for derived classes).
- For each constant field (public or private), its initialization with this set is evaluated to the class itself and the property is created on the class.
- Static initialization blocks are evaluated with this set in the class itself.
Examples
Connecting this with instance and static methods
When a static or instance method is called without a value for it, for example by assigning the method to a variable and then calling it, the value will be undefined inside the method. This behavior is the same even if there is no "using strict" directive, because code inside the class body is always executed in strict mode.
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
const obj = new Animal();
obj.speak(); // the Animal object
const speak = obj.speak;
speak(); // undefined
Animal.eat(); // class Animal
const eat = Animal.eat;
eat(); // undefinedIf we rewrite the above using traditional function-based syntax in the non-strict case, then calls to this method are automatically restricted to globalThis. In the strict case, the value of this remains undefined.
function Animal() {}
Animal.prototype.speak = function () {
return this;
};
Animal.eat = function () {
return this;
};
const obj = new Animal();
const speak = obj.speak;
speak(); // global object (in non–strict mode)
const eat = Animal.eat;
eat(); // global object (in non-strict mode)









