Рубріки: Теория

Почему классы JS это не просто «синтаксический сахар»

Богдан Мирченко

JS-инженер Андреа Джаммарки написал на medium.com пост, в котором назвал заблуждением утверждение, что классы JS — это просто синтаксический сахар для прототипного наследования. По его мнению, есть множество вещей, которые можно смоделировать с помощью ES5 и прототипного наследования, но ни один из этих методов не настолько быстрый и безопасный, как использование соответствующего синтаксиса для классов JS.

Вот как он обосновал позицию в пользу использования классов JS:

  • Использование директивы use strict в ES5 не запрещает вызов конструкторов без служебного слова new.
// ES5
function Test() { "use strict"; }
Test.call({}); // it's OK

// ES6+
class Test {}
Test.call({}); // it throws

Современные классы имеют свойство new.target, которое невозможно сымитировать в ES5 без использования транспайлера, копирующего такое поведение.

  • Андреа Джаммарки отмечает, что, несмотря на его попытки создать подклассы для массивов, невозможно расширить внутренние функции в ES5 полезным или значимым образом.
// ES5 epic fail
function List() { "use strict"; }
List.prototype = Object.create(Array.prototype);

var list = new List;
list.push(1, 2, 3);

JSON.stringify(list);
// {"0":1,"1":2,"2":3,"length":3}

list.slice(0) instanceof List; // false

По словам автора, он не использует в конструкторе свойство Array.apply(this, arguments), так как расширение массива ES5 неудобно.

// ES5 epic fail v2
function Text(value) {
  "use strict";
  String.call(this, value);
}

new Text("does this work?");
// nope, it doesn't ... no way it can.

С помощью прототипного наследования не получится расширить String, а с помощью классов JS можно

  • list.slice (0) не является экземпляром List из-за свойства Symbol.species
// ES6+
class List extends Array {}

(new List).slice(0) instanceof List; // true
[].slice.call(new List) instanceof List; // true
  • В конструкторе ES5 не работает свойство Array.apply(this, arguments), потому что:

       — встроенный массив создает новый массив, которому не важен контекст и другие встроенные функции;

       — классы JS обновляют экземпляры, что невозможно сделать в ES5 без транспайлера.

// ES5
function Button() {
  return document.createElement('button');
}

function MyButton(value) {
  Button.call(this);
  this.textContent = value;
}

Object.setPrototypeOf(MyButton, Button);
Object.setPrototypeOf(MyButton.prototype, Button.prototype);
  • При вызове свойства new MyButton(“content”) возвращается экземпляр MyButton со свойством textContent.
function MySubClass() {
  var self = Class.apply(this, arguments) || this;
  // do anything with the self
  return self;
}

Этот подход плох тем, что:

       — если суперкласс возвращает что-то еще, теряется наследование;

       — если суперкласс является встроенным, вместо этого может быть команда self, указывающая на примитив.

Как в этом случае работают классы JS:

// ES6+
class Button {
  constructor() {
    return document.createElement('button');
  }
}
class MyButton extends Button {
  constructor(value) {
    super();
    this.textContent = value;
  }
}
document.body.appendChild(new MyButton("hello"));
  • В классах JS методы не могут быть построены, как и сокращенные буквальные методы.
// ES6+
class Test {
  method() {}
}
new Test.prototype.method;
// TypeError: Test.prototype.method is not a constructor

В ES5 все функции могут использоваться как конструкторы.

// ES5
function Test() {}
Test.prototype.method = function () {
  if (!(this instanceof Test))
    throw new TypeError("not a constructor");
};
  • В классах JS нельзя перечислить статические и нестатические методы. ES5 позволяет это сделать, но boilerplate-код будет огромный, медленный и неудобный.
  • В классах JS стрелочные функции можно указывать в определении класса. То же самое действие в ES5 будет чересчур громоздким:
// ES5
function WithArrows() {
  Object.defineProperties(this, {
    method1: {
      configurable: true,
      writable: true,
      value: () => "arrow 1"
    }
  });
}

// ES6+
class WithArrows {
  method1 = () => "arrow 1";
}

// (new WithArrows).method1();
  • В классах JS есть частные свойства и частные методы.
// ES6+
class WithPrivates {
  #value;
  #method(value) {
    this.#value = value;
  }
  constructor(value) {
    this.#method(value);
  }
}

В ES5 можно смоделировать частные свойства, только используя транспайлер и коллекции WeakMap.

Автор призывает перестать называть классы JS просто «синтаксическим сахаром». По мнению Андреа Джаммарки, множество вещей можно симулировать с помощью прототипного наследования, но некоторые просто невозможно. Использование современных классов из ES6 поможет создать лучшее ООП в JS, чем оно было в течение последних 20 лет.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023