Класи

Класи в JavaScript були додані в стандарті ECMAScript 6 і є насамперед синтаксичним цукром для існуючого прототипного наслідування у JavaScript. Синтаксис класів не впроваджує нову модель об'єктно-орієнтованого наслідування у JavaScript.

Визначення класів

Класи - це, по факту, "особливі функції", і так само, як ви можете визначити функціональний вираз та оголошення функції, синтаксис класу складається з двох компонентів: виразу класу and оголошення класу.

Оголошення класу

Одним зі способів визначення класу є оголошення класу. Для оголошення класу використовується ключове слово class з назвою класу (у прикладі нижче "Rectangle").

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

Підняття

Важлива відмінність між оголошеннями функції та класу полягає в тому, що оголошення функції піднімається, а оголошення класу ні. Спочатку необхідно оголосити клас, і тільки потім звертатись до нього, інакше подібний код викине помилку ReferenceError:

const p = new Rectangle(); // ReferenceError

class Rectangle {}

Вираз класу

Вираз класу - це інший спосіб визначення класу. Вираз класу може бути іменованим або неіменованим. Ім'я, надане іменованому виразу класу, є локальним для тіла класу. (Хоча його можна отримати через властивість класу (не екземпляра) name).

// не іменований
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// виведе "Rectangle"

// іменований
let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// виведе "Rectangle2"

Заувага: Вирази класів підпадають під такі самі обмеження підняття, які описані у розділі Оголошення класу.

Тіло класу та визначення методів

Тіло класу - це та частина, що знаходиться у фігурних дужках {}. Тут ви визначаєте складові класу, такі як методи або конструктор.

Строгий режим

Код у тілі класу виконується у строгому режимі, тобто, код, записаний тут, є предметом більш строгого синтаксису заради підвищення продуктивності. Деякі помилки, які пройшли б непоміченими, будуть викидатися, а певні ключові слова є зарезервованими для майбутніх версій ECMAScript.

Конструктор

Метод constructor - це спеціальний метод для створення та ініціалізації об'єктів, створених ключовим словом class. У класі може бути лише один спеціальний метод з ім'ям "constructor". Якщо клас містить більше одного метода constructor, виникне помилка SyntaxError.

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

Методи прототипу

Дивіться також визначення методів

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 визначає статичний метод класу. Статичні методи класу викликаються без створення екземпляра свого класу і не можуть бути викликані через екземпляр класу. Статичні методи часто використовують для створеня функцій-утиліт для застосунків.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    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.distance; //undefined
p2.distance; //undefined

console.log(Point.distance(p1, p2)); // 7.0710678118654755

Пакування через прототипні та статичні методи

Коли статичний або прототипний метод викликається без значення для this, значенням this всередині метода буде undefined. Ця поведінка буде такою самою навіть якщо директива "use strict" не задана, тому що код в межах тіла класу завжди виконується у строгому режимі.

class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined

Якщо наведений приклад написаний з використанням традиційного синтаксису функцій, то автоматичне пакування при викликах методів відбуватиметься у нестрогому режимі, на основі початкового значення this. Якщо початковим значенням є undefinedthis буде присвоєний глобальний об'єкт.

Автопакування не відбудеться у строгому режимі, значення this залишиться незмінним.

function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

Властивості екземпляра

Властивості екземпляра повинні визначатися всередині методів класу:

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

Статичні (на боці класу) властивості-значення та прототипні властивості-значення повинні визначатися за межами оголошення класу:

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); // викликати конструктор суперкласу та передати параметром ім'я
  }

  speak() {
    console.log(`${this.name} гавкає.`);
  }
}

let d = new Dog('Мінні');
d.speak(); // Мінні гавкає.

Якщо у підкласі присутній конструктор, він має спочатку викликати super(), перед використанням "this".

Можна також розширювати через extend традиційні "класи" на основі функцій:

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(); // Мінні гавкає.

//Заувага: Для однойменних методів, дочірній метод переважає над батьківським

Зауважте, що класи не можуть розширювати звичайні об'єкти (не конструктори). Якщо бажаєте успадкуватись від звичайного об'єкта, можете натомість використати Object.setPrototypeOf():

const Animal = {
  speak() {
    console.log(`${this.name} видає звуки.`);
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

// Якщо ви цього не зробите, то отримаєте помилку, викликавши speak
Object.setPrototypeOf(Dog.prototype, Animal);

let d = new Dog('Мінні');
d.speak(); // Мінні видає звуки.

Species

Можливо, ви захочете повернути об'єкти Array у своєму похідному класі масиву MyArray. Шаблон species дозволяє перевизначати початкові конструктори.

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

class MyArray extends Array {
  // Заміщення Symbol.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 + ' ричить.');
  }
}

Домішки

Абстрактні підкласи, або домішки, це шаблони для створення класів. В ECMAScript підклас може мати лише один суперклас, тому множинне наслідування, наприклад, кількох класів інструментів, неможливе. Функціональність має бути надана суперкласом. 

У ECMAScript для реалізації домішок може використовуватись функція з суперкласом в якості параметра та підкласом, що розширює цей суперклас, в якості значення, що повертається:

let calculatorMixin = Base => class extends Base {
  calc() { }
};

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

Клас, що використовує ці домішки, може бути записаний так:

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

Специфікації

Специфікація Статус Коментар
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Class definitions' in that specification.
Standard Початкове визначення.
ECMAScript 2016 (ECMA-262)
The definition of 'Class definitions' in that specification.
Standard
ECMAScript 2017 (ECMA-262)
The definition of 'Class definitions' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'Class definitions' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
classesChrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13Firefox Full support 45IE No support NoOpera Full support 36
Notes
Full support 36
Notes
Notes From Opera 29 to 35, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Safari Full support 9WebView Android Full support 49
Notes
Full support 49
Notes
Notes From WebView 42 to 48, strict mode is required.
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45Opera Android Full support 36
Notes
Full support 36
Notes
Notes From Opera 29 to 35, strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Safari iOS Full support 9Samsung Internet Android Full support 5.0
Notes
Full support 5.0
Notes
Notes In Samsung Internet 4.0, strict mode is required.
nodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
constructorChrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13Firefox Full support 45IE No support NoOpera Full support 36Safari Full support 9WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45Opera Android ? Safari iOS Full support 9Samsung Internet Android Full support Yesnodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
extendsChrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13Firefox Full support 45IE No support NoOpera Full support 36Safari Full support 9WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45Opera Android ? Safari iOS Full support 9Samsung Internet Android Full support Yesnodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
Private class fieldsChrome Full support 74Edge No support NoFirefox No support NoIE No support NoOpera Full support 62Safari No support NoWebView Android Full support 74Chrome Android Full support 74Firefox Android No support NoOpera Android Full support 53Safari iOS No support NoSamsung Internet Android No support Nonodejs Full support 12.0.0
Public class fieldsChrome Full support 72Edge No support NoFirefox Full support 69IE No support NoOpera Full support 60Safari No support NoWebView Android Full support 72Chrome Android Full support 72Firefox Android No support NoOpera Android Full support 51Safari iOS No support NoSamsung Internet Android No support Nonodejs Full support 12.0.0
staticChrome Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Edge Full support 13Firefox Full support 45IE No support NoOpera Full support 36Safari Full support 9WebView Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Chrome Android Full support 49
Notes
Full support 49
Notes
Notes From Chrome 42 to 48 strict mode is required. Non-strict mode support can be enabled using the flag "Enable Experimental JavaScript".
Firefox Android Full support 45Opera Android ? Safari iOS Full support 9Samsung Internet Android Full support Yesnodejs Full support 6.0.0
Full support 6.0.0
Full support 4.0.0
Disabled
Disabled From version 4.0.0: this feature is behind the --use_strict runtime flag.
Full support 5.0.0
Disabled
Disabled From version 5.0.0: this feature is behind the --harmony runtime flag.
Static class fieldsChrome Full support 72Edge No support NoFirefox No support No
Notes
No support No
Notes
Notes Static fields aren't supported, see bug 1535804.
IE No support NoOpera Full support 60Safari No support NoWebView Android Full support 72Chrome Android Full support 72Firefox Android No support NoOpera Android Full support 51Safari iOS No support NoSamsung Internet Android No support Nonodejs Full support 12.0.0

Legend

Full support  
Full support
No support  
No support
Compatibility unknown  
Compatibility unknown
See implementation notes.
See implementation notes.
User must explicitly enable this feature.
User must explicitly enable this feature.

Запуск у Чернетці

Клас не можна перевизначати. Якщо ви працюєте з кодом у Чернетці (меню Firefox Інструменти > Веб-розробка > Чернетка), і 'Запускаєте' визначення класу з тим самим ім'ям двічі, ви отримаєте дивну помилку SyntaxError: redeclaration of let <ім'я-класу>.

Щоб перезапустити визначення, використовуйте меню Чернетки Виконати > Перезавантажити і запустити.
Будь ласка, голосуйте за помилку #1428672.

Див. також