1. ES6 классы
JavaScript является единственным массовым языком программирования с наследованием, основанным не на классах, а на прототипах. Огромное число разработчиков, владеющих техникой использования классов, постоянно пытались переделать JavaScript под свои умения, а главное, под имеющиеся наработки в области ООП. В ES6 сделали шаг навстречу и ввели класс, похожий на таковой в языках Java, C# и т. п. Это все те же прототипы, только в красивой обертке.
2. Объявление класса
Класс — это удобный способ для задания конструктора вместе с прототипом.
Преимущества использования классов:
- Весь код внутри класса выполняется в строгом режиме.
- Все методы являются неперечислимыми.
- У всех методов класса отсутствует внутренний метод
[[Construct]], что означает появление ошибки при попытке использовать эти методы с операторомnew. - Вызов конструктора класса без оператора
newвызовет ошибку.
Предположим, мы запустили отель и хотим, чтобы объект со свойствами и методами представлял каждого гостя.
//class declaration
class Guest {
//...
}
// Под капотом класс это функция-конструктор с прототипом
console.log(typeof Guest); // "function"
console.log(Guest.prototype); // {constructor: ƒ}
const guest = new Guest();
console.log(guest); // Guest {}
Как строить класс, зависит от того, что вам нужно. В нашем случае, класс представляет собой гостя отеля, поэтому мы добавим туда поля для имени и номера комнаты.
Класс использует аналог функции-конструктора — метод constructor. Это особый
метод, он должен присутствовать в объявлении класса обязательно. Назначение
конструктора — создавать собственные свойства экземпляра класса.
- Функция
constructorзапускается при вызовеnew Guest, остальные методы записываются вGuest.prototype. - Свойство
prototypeкласса доступно только для чтения, т.е. это свойство нельзя изменить. - В классе должен быть только один конструктор, иначе будет ошибка.
class Guest {
// аналог функции-конструктора
constructor(name, roomNumber) {
this.name = name;
this.roomNumber = roomNumber;
}
}
const mango = new Guest('Mango', 26);
console.log(mango); // {name: Mango, roomNumber: 26}
console.log(mango instanceof Guest); // true
console.log(mango instanceof Object); // true
3. Методы
Добавим функцию getFullInfo. Функции, определенные таким образом, называться
методами класса и доступны всем экземплярам через prototype.
class Guest {
// Аналог функции-конструктора
constructor(name, roomNumber) {
this.name = name;
this.roomNumber = roomNumber;
}
// Аналог Guest.prototype.getFullInfo
getFullInfo() {
console.log(`
Guest ${this.name}
Room number ${this.roomNumber}
`);
}
}
const mango = new Guest('Mango', 26);
mango.getFullInfo();
// Guest Mango
// Room number 26
4. Геттеры и сеттеры
Собственные свойства класса желательно хранить в constructor. Но существует и
другой вариант — разместить в теле класса методы set и get. Эти методы
используют особый синтаксис, а при их вызове, после имени объекта следует
написать только имя геттера или сеттера без вызова функции, т.е. без круглых
скобок.
class Guest {
// Собственные свойства класса размещаем в конструкторе
constructor(name, roomNumber) {
this.name = name;
this.roomNumber = roomNumber;
}
// Используем геттеры и сеттеры для описания интерфейса доступа к свойствам
get name() {
return this.name;
}
set name(value) {
this.name = value;
}
}
const mango = new Guest('Mango', 26);
// обращение к get и set не требует вызова - т.е. без ()
console.log(mango.name); // Mango
mango.name = 'Mango the Fluffy';
console.log(mango.name); // Mango the Fluffy
5. Статические свойства и методы
Можно создавать собственные свойства класса и собственные методы класса для
вызова по имени класса без создания объекта. Такие свойства и методы называют
статическими. Для их создания в классе перед свойством или методом нужно
добавить служебное слово static.
Конструктор класса тоже метод, но его нельзя делать статическим, это вызовет ошибку.
// Класс со статическими свойствами и методами
class Calc {
// Класс-калькулятор для двух аргументов
constructor() {}
// Метод как замена свойству
static get PI() {
return 3.14;
}
// Статический метод +
static add(...args) {
return args.reduce((acc, next) => acc + next, 0);
}
// Статический метод *
static mult(...args) {
return args.reduce((acc, next) => acc * next, 1);
}
}
console.log(Calc.PI); // 3.14
console.log(Calc.add(2, 3, 4)); // 9
console.log(Calc.mult(12, 3, 4)); // 144
6. Наследование
JavaScript позволяет реализовать наследование классов. Это означает, что мы можем создавать новые классы, которые наследуют все от другого класса, но при этом добавляют уникальную информацию для себя.
/*
* Ключевое слово extends указывает на родительский класс,
* чьи свойства будут унаследованы.
*/
class Child extends Parent {
// ...
}
Создадим базовый класс-родитель Animal от которого класс Dog будет
наследовать.
class Animal {
constructor(name) {
this.name = name;
}
move() {
console.log(`I, ${this.name}, am moving!`);
}
}
class Dog extends Animal {
constructor(name, breed) {
// Вызвать конструктор Animal с аргументом name
super(name);
this.breed = breed;
}
bark() {
console.log('woof!');
}
moveAndMakeSound() {
super.move();
this.bark();
}
}
const dog = new Dog('Mango', 'shepherd');
dog.move(); // I, Mango, am moving!
dog.bark(); // woof!
dog.moveAndMakeSound(); // I, Mango, am moving! woof!
При наследовании через extends формируется стандартная цепочка прототипов:
методы Dog находятся в Dog.prototype, методы Animal в Animal.prototype и
они связаны через __proto__.
Конструктор родителя наследуется автоматически. То есть, если в потомке не
указан свой constructor, то используется родительский. Если же у потомка свой
constructor, то чтобы в нём вызвать конструктор родителя, используется метод
super() с аргументами для constructor родителя.
- Вызвать конструктор родителя можно только изнутри конструктора потомка. В
частности,
super()нельзя вызвать из произвольного метода. - В конструкторе потомка мы обязаны вызвать
super()до первого обращения к ключевому словуthis. До вызоваsuper()не существуетthis, так как по спецификации в этом случае именноsuperинициализируетthis. super()можно вызывать только в наследующем классе, иначе будет ошибка.- При наследовании, вызов конструктора родителя осуществляется через
super(...args), вызов родительских методов черезsuper.method(...args).