Классы
======

Baseline Widely available
-------------------------

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

* Learn more
* See full compatibility
* Report feedback
Классы в JavaScript были введены в ECMAScript 2015 и представляют собой синтаксический сахар над существующим в JavaScript механизмом прототипного наследования. Синтаксис классов **не вводит** новую объектно-ориентированную модель, а предоставляет более простой и понятный способ создания объектов и организации наследования.

Определение классов
-------------------

На самом деле классы — это "специальные функции", поэтому точно также, как вы определяете функции (function expressions и function declarations), вы можете определять и классы с помощью: class declarations и class expressions.

### Объявление класса

Первый способ определения класса — **class declaration (*объявление класса*)**. Для этого необходимо воспользоваться ключевым словом `class` и указать имя класса (в примере — «Rectangle»).


```js
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
```


#### Подъём (hoisting)

Разница между *объявлением функции* (*function declaration*) и *объявлением класса* (*class declaration*) в том, что *объявление функции* совершает подъём (hoisting), в то время как *объявление класса* — нет. Поэтому вначале необходимо объявить ваш класс и только затем работать с ним, а код же вроде следующего сгенерирует исключение типа `ReferenceError`:


```js
var p = new Rectangle(); // ReferenceError

class Rectangle {}
```

### Выражение класса

Второй способ определения класса — **class expression (*выражение класса*)**. Можно создавать именованные и безымянные выражения. В первом случае имя выражения класса находится в локальной области видимости класса и может быть получено через свойства самого класса, а не его экземпляра.


```js
// безымянный
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` — специальный метод, необходимый для создания и инициализации объектов, созданных, с помощью класса. В классе может быть только один метод с именем `constructor`. Исключение типа `SyntaxError` будет выброшено, если класс содержит более одного вхождения метода `constructor`.

Ключевое слово `super` можно использовать в методе `constructor` для вызова конструктора родительского класса.

### Методы прототипа

Смотрите также определение методов.


```js
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`, определяет статический метод или свойства для класса. Статические методы и свойства вызываются без инстанцирования (en-US) их класса, и **не могут** быть вызваны у экземпляров (*instance*) класса. Статические методы, часто используются для создания служебных функций для приложения, в то время как статические свойства полезны для кеширования в рамках класса, фиксированной конфигурации или любых других целей, не связанных с реплецированием данных между экземплярами.


```js
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 внутри вызываемой функции. Поведение будет таким же даже без директивы `"use strict"`, потому что код внутри тела класса всегда выполняется в строгом режиме.


```js
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 останется прежним.


```js
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` используется в *объявлениях классов* и *выражениях классов* для создания класса, дочернего относительно другого класса.


```js
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.

Аналогичным образом можно расширять традиционные, основанные на функциях "классы":


```js
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) объекты. Если вам необходимо создать наследование от обычного объекта, в качестве замены можно использовать `Object.setPrototypeOf()`:


```js
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(); // Митци издаёт звук.
```

Species
-------

Допустим, вам хотелось бы возвращать объекты типа `Array` в вашем производном от массива классе `MyArray`. Паттерн species позволяет вам переопределять конструкторы по умолчанию.

Например, при использовании таких методов, как `map()`, который возвращает конструктор по умолчанию, вам хотелось бы, чтобы они возвращали родительский объект `Array` вместо объекта `MyArray`. Символ `Symbol.species` позволяет это реализовать:


```js
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` используется для вызова функций на родителе объекта.


```js
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
-------

Абстрактные подклассы, или mix-ins, — это шаблоны для классов. У класса в ECMAScript может быть только один родительский класс, поэтому множественное наследование (к примеру, от tooling classes) невозможно. Функциональность должен предоставлять родительский класс.

Для реализации mix-ins в ECMAScript можно использовать функцию, которая в качестве аргумента принимает родительский класс, а возвращает подкласс, его расширяющий:


```js
var calculatorMixin = (Base) =>
  class extends Base {
    calc() {}
  };

var randomizerMixin = (Base) =>
  class extends Base {
    randomize() {}
  };
```


Класс, использующий такие mix-ins, можно описать следующим образом:


```js
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
```

Спецификации
------------

| Specification |
| --- |
| ECMAScript Language Specification # sec-class-definitions |

Совместимость с браузерами
--------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

Повторное определение класа
---------------------------

Класс не может быть переопределён. Попытка этого приведёт к `SyntaxError` .

Если мы запускаете код в веб браузере, к примеру в Firefox Web Console (**Tools** > **Web Developer** > **Web Console**) и вы используете ('Run') определение класса с одним и тем же именем дважды, вы получите `SyntaxError: redeclaration of let ClassName;`. (Обсуждение по ошибке можно посмотреть в Firefox bug 1428672.) Chrome Developer Tools возвращает сообщение типа `Uncaught SyntaxError: Identifier 'ClassName' has already been declared at <anonymous>:1:1`.

Смотрите также
--------------

* Функции
* Определение классов
* Выражение классов
* Публичные поля класса
* Приватные поля класса
* super
* Статья в блоге: "ES6 In Depth: Classes"
* Fields and public/private class properties proposal (stage 3)