ES6 классы

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 {}
Copy

Как строить класс, зависит от того, что вам нужно. В нашем случае, класс представляет собой гостя отеля, поэтому мы добавим туда поля для имени и номера комнаты.

Класс использует аналог функции-конструктора — метод 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
Copy

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
Copy

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
Copy

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
Copy

6. Наследование

JavaScript позволяет реализовать наследование классов. Это означает, что мы можем создавать новые классы, которые наследуют все от другого класса, но при этом добавляют уникальную информацию для себя.

/*
 * Ключевое слово extends указывает на родительский класс,
 * чьи свойства будут унаследованы.
 */
class Child extends Parent {
  // ...
}
Copy

Создадим базовый класс-родитель 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!
Copy

При наследовании через 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).

7. Дополнительные материалы

Last updated