--- title: Классы slug: Web/JavaScript/Reference/Classes tags: - ECMAScript 2015 - ECMAScript6 - JavaScript - Reference - Классы - Наследование translation_of: Web/JavaScript/Reference/Classes ---
Классы в JavaScript были введены в ECMAScript 2015 и представляют собой синтаксический сахар над существующим в JavaScript механизмом прототипного наследования. Синтаксис классов не вводит новую объектно-ориентированную модель, а предоставляет более простой и понятный способ создания объектов и организации наследования.
На самом деле классы — это "специальные функции", поэтому точно также, как вы определяете функции (function expressions и function declarations), вы можете определять и классы с помощью: class declarations и class expressions.
Первый способ определения класса — class declaration (объявление класса). Для этого необходимо воспользоваться ключевым словом class
и указать имя класса (в примере — «Rectangle»).
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Разница между объявлением функции (function declaration) и объявлением класса (class declaration) в том, что объявление функции совершает подъём ({{Glossary("Hoisting", "hoisted")}}), в то время как объявление класса — нет. Поэтому вначале необходимо объявить ваш класс и только затем работать с ним, а код же вроде следующего сгенерирует исключение типа {{jsxref("ReferenceError")}}:
var p = new Rectangle(); // ReferenceError
class Rectangle {}
Второй способ определения класса — class expression (выражение класса). Можно создавать именованные и безымянные выражения. В первом случае имя выражения класса находится в локальной области видимости класса и может быть получено через свойства самого класса, а не его экземпляра.
// безымянный
var Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// отобразится: "Rectangle"
// именованный
var Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// отобразится: "Rectangle2"
Обратите внимание: выражения класса подвержены тем же проблемам с подъёмом (hoisting), что и объявления класса.
Тело класса — это часть кода, заключенная в фигурные скобки {}
. Здесь вы можете объявлять члены класса, такие как методы и конструктор.
Тела объявлений классов и выражений классов выполняются в строгом режиме (strict mode).
Метод constructor
— специальный метод, необходимый для создания и инициализации объектов, созданных, с помощью класса. В классе может быть только один метод с именем constructor
. Исключение типа {{jsxref("SyntaxError")}} будет выброшено, если класс содержит более одного вхождения метода constructor
.
Ключевое слово super
можно использовать в методе constructor
для вызова конструктора родительского класса.
См. также определение методов.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
Ключевое слово static
, определяет статический метод или свойства для класса. Статические методы и свойства вызываются без инстанцирования их класса, и не могут быть вызваны у экземпляров (instance) класса. Статические методы, часто используются для создания служебных функций для приложения, в то время как статические свойства полезны для кеширования в рамках класса, фиксированной конфигурации или любых других целей, не связанных с реплецированием данных между экземплярами.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Точка";
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); // "Точка"
console.log(Point.distance(p1, p2)); // 7.0710678118654755
this
в прототипных и статических методахКогда статический или прототипный метод вызывается без привязки к this объекта (или когда this является типом boolean, string, number, undefined, null), тогда this будет иметь значение undefined внутри вызываемой функции. Автоупаковка не будет произведена. Поведение будет таким же как если бы мы писали код в нестрогом режиме.
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // объект Animal
let speak = obj.speak;
speak(); // undefined
Animal.eat() // класс Animal
let eat = Animal.eat;
eat(); // undefined
Если мы напишем этот же код используя классы основанные на функциях, тогда произойдет автоупаковка основанная на значении this, в течение которого функция была вызвана. В строгом режиме автоупаковка не произойдет - значение this останется прежним.
function Animal() { }
Animal.prototype.speak = function(){
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // глобальный объект (нестрогий режим)
let eat = Animal.eat;
eat(); // глобальный объект (нестрогий режим)
Свойства экземпляра должны быть определены в методе класса:
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } }
Статические (class-side) свойства и свойства прототипа должны быть определены за рамками тела класса:
Rectangle.staticWidth = 20; Rectangle.prototype.prototypeWidth = 25;
Публичные и приватные поля - это экспериментальная особенность (stage 3), предложенная комитетом TC39 по стандартам языка Javascript. Поддержка баузерами ограничена, но это нововведение может быть использовано на моменте сборки, используя к примеру Babel.
Используя Javascript синтаксис определения полей, приведенный выше пример может быть изменен следующим образом:
class Rectangle { height = 0; width; constructor(height, width) { this.height = height; this.width = width; } }
Как видно из примера, поля могут быть объявлены как со начальным значением, так и без него.
Более подробно об этом написано в публичные поля класса.
Предыдущий пример может быть изменен следующим образом, используя приватные поля:
class Rectangle { #height = 0; #width; constructor(height, width) { this.#height = height; this.#width = width; } }
Приватные поля могут быть изменены или прочитаны только в рамках класса и не могут быть вызваны извне. Определяя вещи, которые не видны за пределами класса, вы гарантируете, что пользователи ваших классов не могут зависеть от внутренних компонентов, которые могут изменить версию на версию.
Приватные поля могут быть объявлены только заранее в объявлении поля.
Приватные поля не могут быть созданы позже путем присваивания им значения, в отличии от обычных свойств.
Более подробно об этом написано в Приватные поля класса.
extends
Ключевое слово extends
используется в объявлениях классов и выражениях классов для создания класса, дочернего относительно другого класса.
class Animal { constructor(name) { this.name = name; }
speak() { console.log(`${this.name} издает звук.`); } } class Dog extends Animal { constructor(name) { super(name); // вызывает конструктор super класса и передает параметр name } speak() { console.log(`${this.name} лает.`); } }let d = new Dog('Митци'); d.speak(); // Митци лает
Если в подклассе присутствует конструктор, он должен сначала вызвать super, прежде чем использовать this.
Аналогичным образом можно расширять традиционные, основанные на функциях "классы":
function Animal (name) { this.name = name; } Animal.prototype.speak = function () { console.log(
`${this.name} издает звук.`); } class Dog extends Animal { speak() { console.log(
`${this.name} лает.`); } } let d = new Dog('Митци'); d.speak(); // Митци лает //
Для аналогичных методов дочерний метод имеет приоритет над родительским.
Обратите внимание, что классы не могут расширять обычные (non-constructible) объекты. Если вам необходимо создать наследование от обычного объекта, в качестве замены можно использовать {{jsxref("Object.setPrototypeOf()")}}:
var Animal = { speak() { console.log(
`${this.name} издает звук.`); } }; class Dog { constructor(name) { this.name = name; } } // Если вы этого не сделаете, вы получите ошибку TypeError при вызове speak. Object.setPrototypeOf(Dog.prototype, Animal); let d = new Dog('Митци'); d.speak(); // Митци издает звук.
Допустим, вам хотелось бы возвращать объекты типа {{jsxref("Array")}} в вашем производном от массива классе MyArray
. Паттерн species позволяет вам переопределять конструкторы по умолчанию.
Например, при использовании таких методов, как {{jsxref("Array.map", "map()")}}, который возвращает конструктор по умолчанию, вам хотелось бы, чтобы они возвращали родительский объект Array
вместо объекта MyArray
. Символ {{jsxref("Symbol.species")}} позволяет это реализовать:
class MyArray extends Array {
// Изменить species на родительский конструктор Array
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
super
Ключевое слово super
используется для вызова функций на родителе объекта.
class Cat { constructor(name) { this.name = name; } speak() { console.log(
`${this.name} издает звук.`); } } class Lion extends Cat { speak() { super.speak(); console.log(
`${this.name}рычит.`); } } let l = new Lion('Фаззи'); l.speak(); // Фаззи издает звук. // Фаззи рычит.
Абстрактные подклассы, или mix-ins, — это шаблоны для классов. У класса в ECMAScript может быть только один родительский класс, поэтому множественное наследование (к примеру, от tooling classes) невозможно. Функциональность должен предоставлять родительский класс.
Для реализации mix-ins в ECMAScript можно использовать функцию, которая в качестве аргумента принимает родительский класс, а возвращает подкласс, его расширяющий:
var calculatorMixin = Base => class extends Base {
calc() { }
};
var randomizerMixin = Base => class extends Base {
randomize() { }
};
Класс, использующий такие mix-ins, можно описать следующим образом:
class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
Спецификация | Статус | Комментарий |
---|---|---|
{{SpecName('ES6', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ES6')}} | Изначальное определение. |
{{SpecName('ESDraft', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ESDraft')}} |
{{Compat("javascript.classes")}}
Класс не может быть переопределен. Попытка этого приведет к SyntaxError
.
Если мы запускаете код в веб браузере, к примеру в Firefox Web Console (Tools > Web Developer > Web Console) и вы используете ('Run') определение класса с одним и тем же именем дважды, вы получите SyntaxError: redeclaration of let ClassName;
. (Обсуждение по ошибке можно посмотреть в {{Bug(1428672)}}.) Chrome Developer Tools возвращает сообщение типа Uncaught SyntaxError: Identifier 'ClassName' has already been declared at <anonymous>:1:1
.