Быстрое знакомство с классами в ES6

9

Classes ES6

В 6 версии ECMAScript появились классы. Эти классы не такие как классы в C# или Java. По факту это синтаксический сахар над прототипным наследованием.

Тело класса состоит из ключевого слова class и названия класса.

class Circle {}

Внутри тела мы можем определить свойства и методы. Один из специальных методов, которые у нас есть — это метод constructor. Мы используем его чтобы инициализировать объект. Также в тело класса поместим и метод. Теперь нам не надо писать function.

class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  draw() {
    console.log("draw");
  }
}

const c = new Circle();

Классы не хоистятся (не поднимаются к началу кода)

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

У классов также присутствуют статические методы. Это методы, которые не зависят от экземпляра, и несут больше инструментальный характер. Т.е. в большинстве случаев, это функции которые делают некоторые расчёты или манипуляции не связанные с конкретным экземпляром класса. Он не будет работать с экземпляром, а будет работать непосредственно с классом. Чтобы вызвать статический метод, нам не нужно создавать экземпляр класса.

Например,

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
  //Instance Method
  draw() {
    console.log("draw");
  }

  //Static Method
  static parse(str) {
    const radius = JSON.parse(str).radius;
    return new Circle(radius);
  }
}
const circle = Circle.parse('{"radius" : 1}');
const c = new Circle();

Или возьмем для примера всеми известный объект Math. По сути его методы являются статическими.

class Math2 {
  static abs(value) {
    //..
  }
}
Math2.abs(5);

This Keyword

Для начала создадим функцию конструктор

const Circle = function() {
  this.draw = function() {
    console.log(this);
  };
};

const c = new Circle();
c.draw();

В консоли мы получим сам объект circle.

Но что будет, если мы передадим метод c.draw в другую переменную и вызовем её от имени новой перменной.

const draw = c.draw;
draw();

Теперь this будет ссылаться на объект window. Когда мы вызываем метод объекта, то если не делать явную привязку, то this будет указывать на объект, от лица которого был вызван метод. Если вызывать функцию обычным способом, то this всегда будет указывать на глобальную область видимости (глобальный объект), либо undefined, если использовать строгий режим use strict. Тело класса в ES6 выполняется в strict mode.

Приватные члены с помощью символов

Вы наверняка помните про абстракцию. Это один из ключевых принципов ООП. Абстракция означает: спрятать детали и сложности, и показать только основные существенные части. Для того чтобы имплементировать абстракцию, мы использовали приватные свойства и методы. Мы скрывали определенные члены объекта, чтобы они были недоступны извне.

И так, у нас есть класс Circle

class Circle {
  constructor(radius) {
    this.radius = radius;
  }
}

У него есть свойство radius. И как вы знаете оно общедоступное по умолчанию, что значит, если мы создадим объект circle, передадим 1 как аргумент, то мы сможем получить доступ к свойству radius без всяких проблем

const c = new Circle(1);
c.radius;

Давайте представим, что мы хотим сделать свойство radius скрытым (private).
Есть 3 основных подхода в ES6, как можно этого достичь.

Первый способ

Первый, самый ебланский — использовать нижнее подчеркивание, как согласованное именование. Некоторые разработчки называют приватные свойства и методы с использованием нижнего подчеркивания. Почему подход ебланский, да потому что всё еще можно получить доступ к приватным методам извне.

Второй способ

Один из нормальных подходов для реализации задуманной затеи — использование символов. Символ — это новый примитивный тип данных, который появился в es6.

Нам нужно создать две константы, и передать в качестве константы символы. Если вызвать символ Symbol(), то он возвратит уникальное значение.

const _radius = Symbol();
const _draw = Symbol();

class Circle {
  constructor(radius) {
    this[_radius] = radius;
  }

  [_draw]() {}
}
const c = new Circle(1);
const key = Object.getOwnPropertySymbols(c)[0];
console.log(c[key]);

В случае с _draw нам пришлось использовать вычисляемый свойство, а в случае с _radius скобочную нотацию.

Третий способ получить приватные свойства и методы

Для третьего способа будем использовать weakMaps, который является новым типом в ES6. Часто используется дл создания приватных свойств и методов.

class Circle {
  constructor(radius) {}
}

Создадим новую константу и передадим ей как значение WeakMap. WeakMap по своей сути это словарь, где ключами могут быть только объекты, а значениями могут быть любые типы данных. Причина почему они называются WeakMap в том, что ключи слабые. Если нигде нет больше ссылок на ключ, то они будут удалены garbage collectorом.

const _radius = new WeakMap();
class Circle {
  constructor(radius) {
    _radius.set(this, radius);
  }
}

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

А как нам реализовать приватный метод. Также создаем новую Слабую Карту. Затем имплементируем метод в конструкторе. Указываем this как ключ и функцию как значение.

Обратите внимание, что нужно использовать стрелочную функцию, иначе вместо this получим undefined, так как теряется контекст при вызове. А стрелочная пользуется внешним this.

const _radius = new WeakMap();
const _move = new WeakMap();

class Circle {
  constructor(radius) {
    _radius.set(this, radius);
    _move.set(this, () => {
      console.log("move", this);
    });
  }

  draw() {
    return _radius.get(this);
  }
}

const c = new Circle(1);
console.log(_radius);
console.log(c);

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

draw() {
    _move.get(this)();
    console.log('draw');
}

Также можно пихать все приватные свойства и методы в один УикМэп.

class Circle {
    constructor(radius) {
        privateProps.set(this, {
            radius: radius,
            move: () => {

            },
            eat: () => {

            },
            fuck: "yes"
        })
    }
}

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

Допустим есть у нас класс с приватным свойством.

const _radius = new WeakMap();

class Circle {
  constructor(radius) {
    _radius.set(this, radius);
    });
  }
}

const c = new Circle(1);

Что, если нам нужно прочитать значение этого свойства извне? Один из вариантов, запилить метод getRadius.

getRadius() {
    return _radius.get(this);
}

Но будет красивее, если мы сможем прочитать его как свойство. Конечно же для этого мы можем использовать getter. Ранее мы уже использовали getter через Object.defineProperty. Однако в ES6 класса всё гораздо проще.

get radius () {
    return _radius.get(this);
}

Также легко определить и setter.

set radius(value) {
    if(value <= 0) throw new Error('invalid radius');
    _radius.set(this, value);
}

Наследование в ES6 классах

Допустим у нас есть два класса Shape и Circle. Как же нам унаследовать Circle от Shape? Всё что нам нужно сделать, написать после Circleextends и название класса от которого мы хотим унаследовать — Shape.

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

Давайте еще представим, что всем нашим Shape объектам нужен color. Добавим это свойство в конструктор класса Shape. Для того, чтобы указать цвет во всех дочерних классах, нужно указать в их конструкторе вызов конструктора родителя через super().

Имейте в виду, что вы не сможете воспользоваться конструктором дочерних объектов, пока не вызовите в их конструкторе конструктор родителя!

class Shape {
  constructor(color) {
    this.color = color;
    this.have = function() {
      console.log('have');
    }
  }
  move() {
    console.log('move');
  }

}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);
    this.radius = radius;
  }
  draw() {
    console.log('draw');
  }
}

const c = new Circle('Green', 5);

Method overriding

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

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

А если нам нужно использовать одноименный метод родителя в каком либо методе дочернем — всё что нам нужно вызвать метод через super.

class Circle extends Shape {
  constructor(color, radius) {
    super(color);
    this.radius = radius;
  }
  draw() {
    console.log('draw');
  }
  move() {
    super.move();
    console.log('Circle move');
  }
}

You might also like More from author

Leave A Reply

Your email address will not be published.