Акт 1: «Ядро» — Часть 14 (Занятия)

Это продолжение серии о JavaScript, начатой ​​здесь.

Классы — это жемчужины принципов объектно-ориентированного программирования. В общем, они обеспечивают хороший способ структурировать код и избежать дублирования кода. Тем не менее, следует отметить, что использование классов и ООП-принципов в целом затмевается тем, что приводит к неподдерживаемому коду, особенно при чрезмерном использовании полиморфизма (наследования).

Классы:

Основная цель классов — улучшить структурирование кода. Код внутри классов автоматически запускается в строгом режиме (см. здесь) и, следовательно, обеспечивает выполнение в безопасном и производительном контексте.

Основные сведения о классах перечислены в этом примере, который объявляет класс с именем MyClass и объявляет несколько полей и методов:

class MyClass {
  #prop1 = 'hello';  // private field
  prop2 = 7;  // public field
  #prop4 = 'private';
  static prop5 = 'I am a static field'; // static field
  // static and private field
  static #prop6 = 'I am static and private'; 
  // dynamic key values (here a symbol)
  [Symbol.iterator] = function () { }; 
  constructor(prop3) {
    this.prop3 = prop3;  // setting non-declared field
  }
  get prop1() { // getter for private field
    return this.#prop1;
  }
  set prop4(v) {  // setter for private field
    this.#prop4 = v;
    console.log(`#prop4 change to '${this.#prop4}'`);
  }
  static get prop6() {  // getter for a static private field
    return MyClass.#prop6;
  }
  publicMethod() {  // public method
    console.log('public method called');
  }
  #privateMethod() {  // private method
  }
  *genFunc() { } // generator functions (iterators)
  static staticMethod() {  // static method
    console.log('static method called');
  }
}

Поля и методы могут быть закрытыми, статическими или общедоступными.

Частные поля и методы имеют префикс ‘#’. Они недоступны извне класса. В приведенном выше примере некоторые частные поля частично доступны с помощью get соотв. set методы.

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

const m = new MyClass(true);
console.log(m.prop1);  // hello
m.prop4 = 'setting private field'; // #prop4 change to 'setting private field'
m.publicMethod(); // public method called
MyClass.staticMethod(); // static method called
console.log(MyClass.prop5); // I am a static field
console.log(MyClass.prop6); // I am static and private

Публичные поля доступны так же, как и обычные объекты: m.prop1

Доступ к статическим методам и полям возможен только через объект функции, то есть имя класса. Важно понимать, что на статические методы и поля ссылаются не из экземпляра данного класса, а непосредственно из самого объекта класса: MyClass.staticMethod() , MyClass.prop5

Все методы, объявленные с использованием сокращенной нотации, являются методами экземпляра, то есть они вызываются через экземпляр: m.publicMethod() Помните, это означает, что при таком вызове его this-контекст устанавливается как экземпляр класса (здесь m ).

В конце концов, экземпляр класса — это не что иное, как объект. Тем не менее, классы предоставляют некоторые сокращенные обозначения для таких вещей, как создание get, set методов.

Сами классы — не что иное, как синтаксический сахар. Все, что вы можете реализовать с помощью классов, вы можете сделать и с помощью новых функций (см. здесь). Тем не менее, классы часто предоставляют гораздо более читаемый код. В этом смысле:

  • get/set соответствуют дескрипторам свойств.
  • статические методы и поля объекта, размещенные в объекте-функции.
  • частные поля и методы, к переменным и функциям, которые объявлены внутри функции ( var, const, let , function ).

Наследование:

Классы позволяют наследовать методы и поля от других классов. Это ограниченная версия наследования прототипов:

class A {
  prop1 = 'prop1 set in A';
  #prop2 = 'prop2';
  prop3 = 'prop3 from A';
  static prop4 = 'static prop4 from A';
constructor(prop1) {
    this.prop1 = prop1;
  }
method1() {
    console.log('method1 from A');
  }
#method2() {
    console.log('method2 from A');
  }
method3() {
    console.log('method3 from B');
  }
}
class B extends A {
  prop3 = 'prop3 from B';
  static prop4 = 'static prop4 from B';
constructor() {
    super('prop1 set through constructor of B')
  }
method3() {
    console.log('method3 from B');
  }
}
const b = new B();
console.log(b.prop1); // prop1 set through constructor of B
b.method1(); // method1 from A
b.method3(); // method3 from B
console.log(b.prop3); // prop3 from B
console.log(B.prop4); // static prop4 from B

Наследование достигается с помощью ключевого слова extend. Конструктор дочернего класса должен вызвать метод super, который ссылается на конструктор родительского класса.

Частные поля и методы не наследуются. Статические методы, хотя и не связанные с экземпляром, наследуются. Не все ОО-языки разделяют это поведение.

Этот конкретный шаблон может применяться глубоко вложенным, то есть вы можете наследовать разные классы от многих классов в иерархии: A1 extends A2 extends A3 ...

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

Предпочитайте композицию наследованию!

Мы не пытаемся рассматривать это здесь, но краткое объяснение «композиции» состоит в том, чтобы иметь небольшие классы (или модули), которые предоставляют выделенную функциональность, которую вы можете внедрить, когда это необходимо.

Оператор instanceof:

Оператор instanceof, применяемый к экземплярам класса, можно использовать для проверки принадлежности экземпляра к определенному классу:

if(b instanceof B){
  console.log('b is a B'); // b is a B
}
if(b instanceof A){
  console.log('b is a A'); // b is a A  (since inherited)
}

Миксины:

Распространенным шаблоном в объектно-ориентированном программировании является так называемый миксин. Он намеревается создать новый класс, используя функциональность данного класса, дополненную некоторыми предопределенными функциями. Поскольку JavaScript позволяет создавать встроенные анонимные классы, этот шаблон можно реализовать следующим образом:

function mixin(B) {
 return class extends B {
   method() {
     console.log(‘decorating method called’);
   }
 }
}
const A = mixin(class { });
new A().method(); // decorating method called

Метод mixin принимает в качестве параметра функцию-конструктор B и возвращает новую функцию-конструктор, которая расширяет B некоторым дополнительным методом. Особенностью этого шаблона является то, что B известен только во время выполнения.

На этом пока все, и не забывайте оставлять комментарии по вопросам или ошибкам, которые вы обнаружите. Давайте посмотрим еще раз в следующей истории, если хотите.

Спасибо за чтение!