Всё о ES6 классах в JavaScript

Александр Мальцев
Александр Мальцев
256
0
Всё о ES6 классах в JavaScript
Содержание:
  1. Что такое классы?
  2. Пример объявления класса
  3. Создание экземпляров класса
  4. Вычисляемые свойства
  5. Наследование и цепочка прототипов
  6. Расширение классов
  7. Переопределение методов
  8. Расширение встроенных классов
  9. Приватные свойства и методы
  10. Геттеры и сеттеры
  11. Статические свойства и методы
  12. Наследование статических свойств и методов
  13. Комментарии

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

Что такое классы?

Начиная с ECMAScript 2015 (ES6) в языке JavaScript появился синтаксис классов. В этом синтаксисе вы можете создавать классы, используя ключевое слово class.

А как же дела обстояли до этого стандарта? Как создавались классы? Их просто не было, так как JavaScript не является классическим объектно-ориентированным языком. Создание объектов осуществлялось с помощью конструкторов или, другими словами, посредством обычных функций, которые вызывались с использованием ключевого слова new.

Под классом в JavaScript мы будем понимать шаблон, на основании которого создаются объекты. Чтобы не путаться, классом будем считать как шаблон, созданный с помощью конструктора, так и с помощью ключевого слова class:

function Person() {}
class Student {}

В этом примере мы создали два класса Person и Student.

Что же с наследованием? В JavaScript имеется только прототипное наследование и работает оно через объекты. С приходом классов в этом плане ничего не изменилось.

Так что же такое class? class – это специальная функция, предназначенная для создания объектов и организации существующего в языке прототипного наследования. То есть class – это просто более новый способ написания шаблона с большим количеством крутых возможностей, который затем можно использовать для создания нужного количества однотипных объектов.

Кстати, проверить, что class является функцией можно с помощью оператора typeof:

class Car {}
typeof Car // 'function'
Проверка типа class с помощью оператора typeof в JavaScript

Пример объявления класса

Допустим, у нас есть блог, на котором пользователи могут публиковать различные посты. Для создания большого количества однотипных объектов, в данном случае постов, следует написать заготовку или иными словами класс. Далее используя его, мы можем сделать экземпляры постов, передавая на вход для их создания определённые данные. После этого посты отобразятся на странице. Каждый экземпляр поста будет иметь свои собственные свойства, такие как текст и количество лайков. Кроме этого, каждый из них будет наследовать метод addLike, с помощью которого мы будем увеличивать количество лайков конкретного поста.

Пример на JavaScript, в котором показан класс и объекты, являющиеся экземплярами данного класса

Таким образом с помощью одного класса (одной заготовки) мы можем создать много однотипных объектов. При этом они будут независимы друг от друга и иметь как свои свойства и методы, так и наследуемые.

Создание класса начинается с ключевого слова class после которого идёт название класса, написанное в нотации PascalCase (первая буква заглавная). Далее между фигурными скобками находится всё что касается этого класса, то есть свойства и методы.

Пример создания класса Post:

class Post {
  constructor(text) {
    this.text = text;
    this.totalLikes = 0;
  }
  addLike() {
    this.totalLikes += 1;
  }
}

В этом классе находятся 2 метода: constructor и addLike. Синтаксис методов очень простой: сначала указывается название метода, далее в круглых скобках при необходимости параметры, а затем его тело в фигурных скобках.

constructor – это специальный метод, который вызывается только при создании новых объектов, в данном случае экземпляров класса Post. В этом методе описываются свойства и методы, которые необходимо непосредственно добавить в создаваемый объект.

В данном примере у конструктора имеется один параметр text. При вызове Post мы можем передать значение этому параметру, то есть аналогично тому, как мы это делаем при вызове функций.

В конструкторе имеется переменная this. Это специальная переменная, которая ссылается на создаваемый объект. То есть, когда вы будете создавать новый экземпляр класса, this автоматически будет указывать на него. С помощью this мы добавляем к этому объекту сначала свойство text со значением, которое мы передали в вызов Post. После этого ещё одно свойство totalLikes но уже со значением 0.

addLike – это метод, посредством которого мы будем увеличивать количество лайков у конкретного объекта типа Post. addLike находится не в самих объектах, а на уровне класса Post, то есть в Post.prototype. Все создаваемые объекты будут иметь доступ к нему посредством наследования.

Пример на JavaScript, в котором показано свойство prototype класса Post, содержащее в данном случае два метода

Почему так? Потому что addLike мы описали вне конструктора. А все методы, описанные вне его, будут находиться на уровне класса, то есть в свойстве prototype.

Откуда взялось это свойство prototype? Это свойство появляется автоматически при объявлении класса. Это прототип, который в дальнейшем будет устанавливаться для всех объектов, создаваемых посредством этого класса. Используются прототипы для наследования. То есть для того чтобы созданные объекты имели доступ к свойствам и методам, расположенных в прототипах.

В данном примере объект Post.prototype имеет 2 метода: constructor и addLike.

Свойство constructor в прототипе содержит сам класс Post:

Post.prototype.constructor === Post // true

Итак, мы создали класс Post. У него имеется 2 метода: constructor и addLike. Конструктор вызывается только при создании нового объекта. Метод addLike будет наследоваться и доступен для каждого экземпляра класса Post.

Создание экземпляров класса

Создание нового экземпляра класса выполняется очень просто с помощью ключевого слова new:

const firstPost = new Post('Какой-то текст');

В этом примере мы переменной firstPost присвоили новый объект, который создали с помощью синтаксиса new Post('Какой-то текст'). Здесь мы вызвали функцию Post посредством указания после неё круглых скобок, а в них передали значение для параметра text. В результате вызовется constructor и ему передадутся на вход эти аргументы. В данном случае один аргумент. На выходе мы получим новый объект с указанными свойствами и методами, и который будет иметь в качестве прототипа объект Post.prototype.

Выведем значение переменной в консоль:

console.log(firstPost);
Пример на JavaScript, в котором мы создали объект класса Post, показали его собственные методы и прототип

Здесь вы видите собственные свойства объекта. Кроме этого, если раскрыть специальное внутреннее свойство [[Prototype]], то мы увидим прототип, а в нём метод addLike. Этот метод находится на уровне класса Post и наследуется всеми его экземплярами.

Соответственно, мы можем вызвать addLike как метод переменной firstPost.

firstPost.addLike();
console.log(firstPost.totalLikes); // 1
firstPost.addLike();
console.log(firstPost.totalLikes); // 2
Пример на JavaScript, в котором мы вызываем метод addLike как метод экземпляра класса Post, при этом данный метод находится в прототипе объекта

Когда мы вызываем addLike, this внутри этого метода будет указывать на объект, который стоит перед точкой. В данном случае this – это будет firstPoint. При первом вызове firstPost.addLike() мы увеличили значение свойства totalLikes объекта firstPost на 1. После второго вызова до 2.

Таким образом не имеет смысла добавлять метод addLike каждому экземпляру класса Post. Достаточно чтобы он наследовался, а для этого нужно просто его добавить в прототип.

С помощью класса можно создать много однотипных объектов:

const secondPost = new Post('Другой текст');
const thirdPost = new Post('Иной текст');

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

Обратите внимание, что конструктор класса Post нельзя вызвать без new:

const fourthPost = Post('Четвёртый пост'); // Uncaught TypeError: Class constructor Post cannot be invoked without 'new'

Вычисляемые свойства

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

const PREFIX = 'itc';
class ShoppingList {
  [PREFIX + 'Products'] = [];
  [PREFIX + 'Add'](item) {
    this[PREFIX + 'Products'].push(item);
  }
}
const day1 = new ShoppingList();
day1[PREFIX + 'Add']('Огурцы 1 кг');
day1[PREFIX + 'Add']('Помидоры 0.5 кг');
console.log(day1[PREFIX + 'Products']);

Так как здесь нам не нужно получать аргументы, то мы опустили конструктор. Но если они нужны, то без конструктора не обойтись. В этом случае необходимо добавить constructor и перечислить в круглых скобках параметры. После этого при создании нового объекта вызовется конструктор и его параметры получат значения, которые мы передали в вызов класса посредством аргументов.

Свойства, объявленные вне конструктора, в отличие от методов не устанавливаются в prototype. Они создаются как собственные свойства объектов перед запуском конструктора.

Наследование и цепочка прототипов

Что такое наследование? Если после переменной firstPost поставить точку, то вы увидите все доступные для этого объекта свойства и методы. Здесь будут как собственные свойства и методы этого объекта, так и наследуемые.

Пример на JavaScript, в котором показаны все доступные для этого объекта свойства и методы

Откуда всё это взялось? Всё из-за того, что объект firstPost имеет ссылку на прототип Post.prototype, а он в свою очередь на свой прототип и так далее. В результате для firstPost получается следующая цепочка прототипов:

firstPost -> Post.prototype -> Object.prototype

Наследование в JavaScript как вы уже знаете осуществляется через прототипы. Таким образом firstPost имеет доступ ко всем этим свойствам и методам. То есть ему доступны не только его свойства и методы, но и те, которые находятся в прототипах Post.prototype и Object.prototype.

Полезные методы:

1. hasOwnProperty. С помощью hasOwnProperty можно проверить принадлежность того или иного свойства конкретному объекту:

firstPost.hasOwnProperty('text') // true
firstPost.hasOwnProperty('addLike') // false

Здесь мы проверяем, есть ли у объекта firstPost собственное свойство, которое называется text. В данном случае мы получаем true. Но при проверке названия addLike, мы получили false. Потому что addLike это не собственный метод объекта firstPost, он находится в Post.prototype.

2. Object.hasOwn. Это статический метод, который появился в стандарте ECMAScript 2022. Он позволяет проверить, есть ли у объекта собственное свойство, то есть делает тоже самое что hasOwnProperty:

Object.hasOwn(firstPost, 'text') // true
Object.hasOwn(firstPost, 'addLike') // false

Object.hasOwn рекомендуется использовать вместо hasOwnProperty.

3. instanceof. Оператор instanceof позволяет проверить принадлежность объекта тому или иному классу с учетом цепочки прототипов:

firstPost instanceof Post // true
firstPost instanceof Object // true
firstPost instanceof Date // false

На второй строке мы тоже получаем true, потому что Object имеется в цепочке прототипов объекта firstPost.

4. isPrototypeOf. С помощью isPrototypeOf мы можем проверить, существует ли объект в цепочке прототипов другого объекта:

Post.prototype.isPrototypeOf(firstPost);

То есть он проверяет, есть ли Post.prototype где-то в прототипной цепочке объекта firstPost.

Расширение классов

В JavaScript мы можем создать дочерний класс, объекты которого будут наследовать прототип родительского класса. Осуществляется это в JavaScript с использованием ключевого слова extends.

Допусти у нас имеется класс Point2D:

class Point2D {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return `(${this.x},${this.y})`;
  }
}

Например, мы хотим, чтобы Point3D расширял класс Point2D. Для этого необходимо использовать ключевое слово extends, после которого указать название родительского класса:

class Point3D extends Point2D {
  constructor(x, y, z) {
    super(x, y);
    this.z = z;
  }
  toString() {
    return `(${this.x},${this.y},${this.z})`;
  }
}

Ключевое слово extends по факту устанавливает в качестве прототипа Point3D.prototype объект Point2D.prototype, то есть следующее:

Point3D.prototype.__proto__ = Point2D.prototype
Наследование классов в JavaScript

В Point3D мы переопределяем конструктор родительского класса. Поэтому перед тем, как обращаться к this нам необходимо сначала вызвать constructor родительского класса. Осуществляется это в JavaScript с помощью ключевого слова super:

constructor(x, y, z) {
  super(x, y);
  this.z = z;
}

В круглых скобках мы передали аргументы для параметров x и y конструктору родительского класса. После этого с помощью this добавили новое свойство z в объект. Затем мы переопределили метод toString, теперь он будет использоваться взамен родительского:

toString() {
  return `(${this.x},${this.y},${this.z})`;
}

Создадим новый экземпляр класса Point3D:

const point = new Point3D(5, 7, 3);

Этот объект будет иметь следующую цепочку прототипов:

point -> Point3D.prototype -> Point2D.prototype -> Object.prototype
Пример на JavaScript, в котором показана цепочка прототипов для объекта point

Вызовем метод toString:

point.toString() // '(5,7,3)'

Обратите внимание, что после extends допускается использовать выражение:

class Point3D extends (() => Point2D)() {}

Кстати, классы в JavaScript являются выражениями. То есть их можно присваивать переменным, передавать в качестве аргументов другим функциям и так далее.

Например:

// класс присвоили переменной Drink
const Drink = class {
  constructor(price, discount = 1) {
    // округлим до двух знаков после запятой
    this.price = Math.round(price * discount * 100) / 100;
  }
}
// присвоим переменной juice созданный объект класса Drink
const juice = new Drink(1.25);
// функция, в которую будем передавать класс и скидку
const drinkWithDiscount = (Base, discount) => {
  // в качестве результата возвращаем новый класс, расширенный от Base с указанной скидкой
  return class extends Base {
    constructor(price) {
      super(price, discount);
    }
  }
}
// переменной Tea присвоим класс, который будет возвращать функция drinkWithDiscount
const Tea = drinkWithDiscount(Drink, 0.8);
// создадим объекты, являющиеся экземплярами класса Tea
const greenTea = new Tea(2);
const blackTea = new Tea(1.5);

Переопределение методов

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

Но нам не всегда нужно полностью переопределять родительский метод. Допустим мы хотим в новом методе просто дописать функционал, которого нет в родительском методе. Тогда чтобы не дублировать код родительского метода мы можем его просто вызвать в дочернем методе. Осуществляется этого в JavaScript с использованием super.

Например:

class Rectangle {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
  getArea() {
    return this.a * this.b;
  }
}
class RightTriangle extends Rectangle  {
  getArea() {
    return super.getArea() / 2;
  }
}
const triangle = new RightTriangle(5, 10);
console.log(triangle.getArea()); // 25

В RightTriangle мы переопределили родительский метод getArea. Для вычисления площади прямоугольного треугольника нам в данном случае можно просто вызвать родительский метод getArea и разделить возвращённое им значение на 2. Вызов родительского метода в JavaScript как уже отмечали выше осуществляется с помощью super:

super.getArea() / 2

Обратите внимание, что в расширяемом классе RightTriangle нет собственного конструктора. Когда в дочернем классе нет конструктора, то используется конструктор родительского класса:

То есть по факту когда нет метода constructor в дочерней класс автоматически добавляется следующее:

class RightTriangle extends Rectangle {
  // конструктор, который будет вызывать родительский конструктор
  constructor(...args) {
    super(...args);
  }
  // ...
}

Рассмотрим ещё один очень интересный пример с переопределением метода:

class Square {
  constructor(left, top, side) {
    this.constructor.counter++;
    this.id = `square-${this.constructor.counter}`;
    this.left = left;
    this.top = top;
    this.side = side;
  }
  draw() {
    document.body.insertAdjacentHTML('beforeend', `<div id="${this.id}" style="position: absolute; top: ${this.top}px; left: ${this.left}px; width: ${this.side}px; height: ${this.side}px; background-color: black;"></div>`);
  }
  static counter = 0;
}
class ColorSquare extends Square {
  constructor(left, top, side, color) {
    super(left, top, side);
    this.color = color;
  }
  draw() {
    super.draw();
    document.querySelector(`#${this.id}`).style.backgroundColor = this.color;
  }
}

const square1 = new Square(100, 100, 50);
square1.draw();
const square2 = new ColorSquare(150, 200, 75, 'green');
square2.draw();

Здесь в дочернем классе мы объявили метод draw и тем самым переопределили метод родительского класса. В draw мы сначала вызываем родительский super.draw(), а затем получаем этот элемент на странице и изменяем его цвет.

Расширение встроенных классов

Создавать дочерние классы можно также от встроенных, которые присутствуют в JavaScript по умолчанию.

Например, расширим Array и добавим в новый класс метод clear, который будет очищать массив от нежелательных значений, таких как undefined, null, '' и так далее:

class ExtArray extends Array {
  clear() {
    return this.filter((el) => el);
  }
}
const arr = new ExtArray(3, null, 7, undefined, '');
console.log(arr.clear()); // [3, 7]

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

После создания нового объекта класса ExtArray, нам будет доступен новый метод clear. Но, кроме этого, ещё все методы массивов и объектов. Происходит это благодаря наследованию. Объект arr имеет следующую цепочку прототипов:

arr -> ExtArray.prototype -> Array.prototype -> Object.prototype

Приватные свойства и методы

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

Кроме публичных мы можем ещё объявлять приватные свойства и методы, которые будут доступны только внутри класса. То есть к ним нельзя обратиться извне, в том числе и из дочерних классов.

Указание свойству или методу того, что он является приватным осуществляется посредством специального символа #:

class Person {
  #name;
  #age;
  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }
  getName() {
    return this.#name;
  }
  getAge() {
    return this.#age;
  }
  setAge(value) {
    if (Number.isInteger(value) && value > 0) {
      this.#age = value;
    }
    throw new Error('Ошибка при установке значения свойству #age! Значение не является целым положительным числом.');
  }
}

Создание объекта john:

const john = new Person('John', 27);
Проверка типа class с помощью оператора typeof в JavaScript

Для организации доступа к приватным свойствам (#name, #age) вместо обычных методов удобно использовать геттеры и сеттеры.

Геттеры и сеттеры

Геттеры и сеттеры – это специальные методы, доступ к которым осуществляется через связанное с каждым из них свойство. При этом геттер работает при чтении свойства, а сеттер – при присвоении свойству некоторого значения. При написании геттера, его необходимо задавать соответственно без параметров, а сеттер – с одним параметром.

Другими словами, геттер и сеттер позволяют создать на вид обычное свойство, но работающее через функции.

Очень часто геттеры и сеттеры применяются соответственно для чтения приватных свойств и присвоения им новых значений:

class Person {
  #name;
  #age;
  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }
  // геттер для получения имени
  get name() {
    return this.#name;
  }
  // геттер для получения возраста
  get age() {
    return this.#age;
  }
  // сеттер для установки возраста
  set age(value) {
    if (Number.isInteger(value) && value > 0) {
      return this.#age = value;
    }
    throw new Error('Ошибка при установке значения свойству #age! Значение не является целым положительным числом.');
  }
}

В этом примере нельзя после создания объекта изменить свойство #name, а присвоение нового значения #age осуществляется только после его успешной проверки.

Для этого мы свойства #name и #age сделали приватными. После этого доступ к свойствам #name и #age реализовали через геттеры. Геттеры отличаются от методов только наличием перед ними ключевого слова get.

Так как #name изменять не нужно, то сеттер написали только для #age. Сеттер начинается с set, затем идёт имя и круглые скобки, в которых обязательно указываем параметр с помощью которого мы будем получать значение для его установки свойству #age.

Как вы уже поняли, свойство, связанное с сеттером или геттером определяется его именем. В этом примере – это name и age.

Создадим объект:

const tom = new Person('Tom', 25);
// получаем имя с помощью сеттера name
console.log(tom.name);
// установим возраст с помощью геттера age
tom.age = 27;
// получим возраст с помощью сеттера age
console.log(tom.age);
// попробуем установить некорректное значение посредством сеттера age
tom.age = -26;
Пример использования геттеров и сеттеров для работы с приватными полями

В этом примере видно что сеттеры и геттеры выглядят как свойства, но в классе они являются методами. Например, сеттер для установки #age проверяет значение. Если оно имеет отрицательную величину, то выбрасывается исключение с помощью throw.

По сути, при объявлении класса геттеры и сеттеры записываются в Person.prototype вот так:

Object.defineProperties(Person.prototype, {
  name: {
    get() { /* ... */ }
  },
  age: {
    get() { /* ... */ },
    set(value) { /* ... */ }
  }
}

Рассмотрим ещё один очень интересный пример. В нём посредством сеттеров и геттеров мы будем соответственно сразу получать несколько свойств и устанавливать им новые значения:

class Point {
  constructor(x, y) {
    [this.x, this.y] = [x, y];
  }
  get xy() {
    return [this.x, this.y];
  }
  set xy(value) {
    [this.x, this.y] = value;
  }
}

const point = new Point(2, 4);
point.xy = [3, 8];
console.log(point.xy); // [3, 8]

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

Свойство, определённое с помощью геттеров и сеттеров находится в прототипе объекта:

Object.getOwnPropertyDescriptor(point, 'xy');
Object.getOwnPropertyDescriptor(point.__proto__, 'xy');
Пример на JavaScript,в котором показывается что геттеры и сеттеры находятся в прототипе объекта

Статические свойства и методы

Статические свойства и методы – это такие, которые принадлежат самому классу, а не объектам, созданным на его основе. То есть они доступны как свойства и методы этого класса и не наследуются его экземплярами.

Создаются статические свойства и методы с использованием ключевого слова static:

class Post {
  constructor(text, date) {
    this.text = text;
    this.date = date;
    this.totalLikes = 0;
    Post.contains.push(this);
  }
  addLike() {
    this.totalLikes += 1;
  }
  // статическое свойство
  static contains = [];
  // статический метод
  static create(text) {
    return new this(text, new Date().toLocaleDateString());
  }
}

В этом примере у нас имеется:

  • статическое свойство contains, в которое мы будем записывать созданные посты;
  • статический метод create для создания нового экземпляр класса Post с текущей датой.

Статические свойства и методы принадлежат самому классу, поэтому обращаться к ним необходимо как к свойствам и методам самого класса. В этом и заключается отличие статических и наследованных свойств и методов. Почему это возможно? Потому что класс – это специальная функция, а функция – это объект.

Вызов статического метода осуществляется таким способом:

// вызовем статического метода
Post.create('Какой-то текст');
// получим количество созданных постов посредством статического свойства
console.log(Post.contains.length);
Пример на JavaScript, в котором мы создали класс со статическими свойствами и методами

Выведем список свойств класса Post с помощью console.dir:

Пример на JavaScript, в выведем в консоль класс Post как объект с помощью console.dir

Метод console.dir позволяет нам увидеть класс Post как объект.

Теперь давайте выведем в консоль Post.prototype:

Пример на JavaScript, в котором выведем в консоль конструктор класса Post чтобы показать, что именно в нём  находятся статические свойства и методы

В этом объекте, который является прототипом для всех экземпляров класса Post статических свойств и методов нет. Но если мы раскроем constructor, то найдём их. То есть статические свойства и методы находятся в конструкторе и не доступны объектам, являющимися экземплярами класса Post.

Наследование статических свойств и методов

В JavaScript при расширении класса посредством extends наследуются его статические свойства и методы. То есть расширенный класс имеет доступ к статическим свойствам и методам родительского класса:

Рассмотрим пример:

class Animal {
  constructor(name) {
    this.name = name;
    this.type = this.constructor.name.toLowerCase();
    let contains = this.constructor.contains[this.type];
    contains = contains ?? [];
    contains.push(this);
  }
  speak() {
    console.log(`I am ${this.type}`);
  }
  static contains = {};
  static getContainsByType() {
    const type = this.name. toLowerCase();
    return this.contains[type];
  }
}
class Dog extends Animal {}
class Cat extends Animal {}

В этом примере у нас имеются три класса Animal, Dog и Cat. В Animal у нас имеется статическое свойство contains и метод getContainsByType. Классы Dog и Cat мы создали посредством расширения родительского класса Animal.

Как работает наследование статических свойств и методов? Как обычно с использованием прототипов. То есть, когда мы, например, создаём класс Dog посредством расширения Animal, то у нас просто создаются 2 ссылки:

  • для экземпляров класса Dog: Dog.prototype.__proto__ = Animal.prototype;
  • для статических свойств и методов: Dog.__proto__ = Animal.

Теперь создадим объекты и проверим наследование статических свойств и методов:

// создадим объекты классов Dog и Cat
new Dog('Несси');
new Dog('Оскар');
new Cat('Пушок');
// выведем в консоль contains как свойство Dog
console.log(Dog.contains);
// вызовем getContainsByType как метод Dog
Dog.getContainsByType();

Но наследование статических свойств и методов характерно только при использовании extends. Например, встроенные классы (Date, Array и другие) не наследуют статические методы родительского класса.

Комментарии: 0