Введение
Классы — это шаблоны для создания объектов. Они инкапсулируют данные вместе с кодом для работы с этими данными. Классы в JS построены на прототипах, но обладают определённым синтаксисом и семантикой, уникальными для классов.
Классы по сути являются “специальными функциями”, и так же, как вы можете определять выражения функций и объявления функций, класс можно определить двумя способами: выражением класса или объявлением класса.
// 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;
}
};Как и объявления функций, объявления классов могут быть анонимными или иметь имя, отличное от имени переменной, которой они назначены. Однако, в отличие от объявлений функций, объявления классов имеют те же временные ограничения «мёртвой зоны», что и let или const, и ведут себя так, как будто они не были сняты.
Тело класса
Тело класса — это часть, заключённая в фигурные скобки {}. Здесь определяются члены класса, такие как методы или конструкторы.
Тело класса выполняется в строгом режиме даже без директивы “use strict”.
Элемент класса можно охарактеризовать тремя аспектами:
- Тип: Приемник, Регулятор, Метод или Полевой
- Местоположение: Статичное или выборочное
- Видимость: публичная или частная
Конструктор
Метод-конструктор — это специальный метод для создания и инициализации объекта, созданного с помощью класса. В классе может быть только один специальный метод с именем “constructor”. Если класс содержит более одного экземпляра метода-конструктора, будет выдана ошибка SyntaxError.
Конструктор может использовать ключевое слово super для вызова конструктора суперкласса.
Вы можете создать свойства экземпляра внутри конструктора:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}В качестве альтернативы, если значения свойств вашего экземпляра не зависят от аргументов конструктора, вы можете определить их как поля класса.
Статические блоки инициализации
Статические блоки обеспечивают гибкую инициализацию статических свойств, включая оценку выражений во время инициализации, при этом сохраняя доступ к частной области действия.
Можно объявить несколько статических блоков, и эти блоки могут перемежаться объявлениями статических полей и методов (все статические элементы оцениваются в порядке объявления).
Методы
Методы определяются в прототипе каждого экземпляра класса и являются общими для всех экземпляров. Методы могут быть простыми функциями, асинхронными функциями, функциями-генераторами или асинхронными функциями-генераторами. Подробнее см. в разделе «Определения методов».
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 определяет статический метод или поле для класса. Статические свойства (поля и методы) определяются в самом классе, а не в каждом экземпляре. Статические методы часто используются для создания вспомогательных функций приложения, в то время как статические поля полезны для кэширования, фиксированной конфигурации или любых других данных, которые не нужно повторять в разных экземплярах.
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.0710678118654755Полевые объявления
Используя синтаксис объявления поля класса, пример конструктора можно записать следующим образом:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}Поля класса подобны свойствам объекта, а не переменным, поэтому для их объявления не используются ключевые слова вроде const. В JavaScript для приватных свойств используется специальный синтаксис идентификаторов, поэтому ключевые слова-модификаторы, такие как public и private, также не следует использовать.
Как видно выше, поля могут быть объявлены как со значениями по умолчанию, так и без них. Поля без значений по умолчанию не определены по умолчанию. Благодаря предварительному объявлению полей определения классов становятся более документированными, а сами поля всегда присутствуют, что способствует оптимизации.
Более подробную информацию см. в разделе Поля открытого класса.
Частная собственность
Используя приватные поля, определение можно изменить следующим образом.
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}Ссылка на закрытые поля извне класса является ошибкой. Они могут быть прочитаны или записаны только внутри тела класса. Определяя объекты, невидимые вне класса, вы гарантируете, что пользователи вашего класса не будут зависеть от внутренних полей, которые могут меняться от версии к версии.
Приватные поля можно объявить только заранее при объявлении поля. Их нельзя создать позже, присваивая им значение, как это возможно с обычными свойствами.
Более подробную информацию смотрите в разделе «Частная собственность».
Наследование
Ключевое слово extends используется в объявлениях классов или выражениях классов для создания класса как дочернего элемента другого конструктора (класса или функции).
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.Если в подклассе есть конструктор, перед его использованием необходимо вызвать метод super(). Ключевое слово super также можно использовать для вызова методов, относящихся к суперклассу.
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.Порядок оценки
При оценке объявления класса или выражения класса его различные компоненты оцениваются в следующем порядке:
- Сначала вычисляется расширенное предложение, если оно есть. Результатом его работы должна быть допустимая функция-конструктор или null, в противном случае выдаётся ошибка TypeError.
- Метод-конструктор извлекается, и если конструктор отсутствует, он заменяется реализацией по умолчанию. Однако, поскольку определение конструктора — это всего лишь определение метода, этот шаг не виден.
- Ключи свойств элементов класса вычисляются в порядке их объявления. Если ключ свойства вычисляется, выражение, вычисляемое с этим значением, устанавливается равным значению, окружающему класс (а не самому классу). Ни одно из значений свойств ещё не вычислялось.
- Методы и методы доступа устанавливаются в порядке их объявления. Методы экземпляра и методы доступа устанавливаются в свойстве прототипа текущего класса, а статические методы и методы доступа — в самом классе. Закрытые методы экземпляра и методы доступа зарезервированы для последующей установки непосредственно в экземпляр. Этот шаг не отображается.
- Теперь класс инициализируется прототипом, заданным расширениями, и реализацией, заданной конструктором. На всех этапах, если вычисляемое выражение пытается получить доступ к имени класса, генерируется исключение ReferenceError, поскольку класс ещё не инициализирован.
- Значения элементов класса вычисляются в порядке объявления:
- Для каждого поля экземпляра (публичного или закрытого) сохраняется выражение инициализатора. Инициализатор вычисляется при создании экземпляра, в начале конструктора (для базовых классов) или непосредственно перед возвратом из вызова super() (для производных классов).
- Для каждого константного поля (публичного или частного) его инициализация с этим набором оценивается для самого класса, и свойство создается в классе.
- Статические блоки инициализации оцениваются с помощью этого набора в самом классе.
Примеры
Связываем это с экземпляром и статическими методами
При вызове статического или экземплярного метода без указания значения, например, путём присвоения метода переменной и последующего вызова, значение внутри метода будет неопределённым. Это поведение сохраняется даже при отсутствии директивы using strict, поскольку код внутри тела класса всегда выполняется в строгом режиме.
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(); // undefinedЕсли переписать вышеприведённый пример, используя традиционный синтаксис, основанный на функциях, в нестрогом случае, то вызовы этого метода автоматически будут ограничены globalThis. В строгом случае значение this останется неопределённым.
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)









