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).

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

results matching ""

    No results matching ""