Pull to refresh

Comments 17

Уважаемые читатели! Пользуетесь ли вы синтаксическими конструкциями ES6 для работы с классами в JavaScript?


Конечно если сидеть на одном JS и не планировать никогда в жизни другие языки (и не использовать другие языки параллельно), тогда можно привыкнуть к его прототипам, и прочее. Но если я в основном пишу на Java-Python-PHP-Ruby-TypeScript-...-ANY_Language, то зачем переключать мозги в JS, когда я могу использовать те же самые конструкции, что и в других языках?

Поэтому, если я могу создать класс, как и в других языках, и работать с ним точно так же, а компиляторы за меня переведут все это дело в более мутный (для меня) JS, то конечно.
Я не особенно пользуюсь Babel, хватает TypeScript, но Babel эти _classCallCheck() и _createClass() пихает в каждый генерируемый класс или опционально можно включить импорт? В TypeScript например все эти вспомогательные функции вынесены в github.com/Microsoft/tslib и при включенном флаге compilerOptions.importHelpers эта библиотека будет импортироваться вместо включения самой реализации функции в каждом месте.
Оптимизация такого уровня требует учёта мельчайших деталей экосистемы JavaScript, влияющих на производительность, в частности, учёта особенностей того, как устроены классы и механизмы наследования в ES6.

кстати, для тех кому важна производительность, не стоит сразу использовать все новые плюшки языка: fhinkel.rocks/six-speed
Меня больше смущает это:
Предупреждение: Изменение прототипа [[Prototype]] объекта является, по самой природе оптимизации доступа к свойствам в современных движках JavaScript, очень медленной операцией, это справедливо для любого браузера и движка JavaScript. Изменение прототипов очень тонко и обширно влияет на производительность, причём это влияние не ограничивается просто временем, проведённым внутри метода Object.setPrototypeOf(), оно может распространяться на любой код, который имеет доступ к любому объекту, чей прототип [[Prototype]] был изменён. Если вы заботитесь о производительности, вы никогда не должны изменять прототип [[Prototype]] объекта. Вместо этого создайте объект с нужным прототипом [[Prototype]], с помощью метода Object.create().
и TypeScript, и Babel используют его. Как с этим обстоят дела?

Они используют его там, где без него никак: для функций. Функция не может быть создана при помощи Object.create.

Почему когда говорят о фичах ES6 никогда не забывают упомянуть, что это «синтаксический сахар». Разве язык не должен абстрагировать нас от деталей реализации виртуальной машины. Сегодня браузеры преобразуют это к старые конструкции. А завтра не будут преобразовывать.
Я не обращал внимания, может в спецификации указано явно, что новые конструкции являются эквивалентом старых. Надо посмотреть. Если нет, то это никакой не синтаксичский сахар.
Когда мы размышляем об объектах, то первое, что приходит в голову — это классы.

Видимо, зря придумали ключевое слово class, оно затмевает сознание и мешает думать об объектах. В JS можно создавать объекты вообще без this, new и class, а только с помощью фабрик, замыканий и, собственно, объектов:


function Queue() {
  const store = [];
  return {
    enqueue(elem) { store.unshift(elem); },
    dequeue() { return store.pop(); },
  }
}

Чем такой способ создания объектов хуже классов?

Не хуже, тут вопрос стандартизации. Если в большинстве языков объекты задаются через class Name {} и вызываются через new Name();, то логичней использовать именно эту структуру, а не то, что Вы написали выше.

Это называется Module Pattren.
Невозможно наследовать. Невозможно мокнуть куски для тестирования. Невозможность распилить код на несколько файлов в таком виде. При разростании это ведёт к такому github.com/Microsoft/TypeScript/blob/master/src/compiler/checker.ts и вот таким попыткам это исправить. github.com/Microsoft/TypeScript/issues/17861 Как думаете стоит ли написать полноценную статью с критикой такого подхода? Единственный сомнительный плюс — тру приваты, до store в вашем случае вообще никак не достучаться снаружи. Но имхо это намеренная потеря гибкости не понятно зачем.
Ну ещё плюс, не надо биндить коллбеки, и так всё замкнуто.
Невозможно наследовать.

Вам действительно нужно наследование? Если без него действительно никак не обойтись, то можно реализовать вот так:


function LimitQueue(limit) {
  const q = Queue();
  const self = {
    enqueue(elem) { q.size() < limit && q.enqueue(elem); }
  };
  return Object.setPrototypeOf(self, q);
}

function Queue() {
  const state = [];
  return {
    enqueue(elem) { state.unshift(elem); },
    dequeue() { return state.pop(); },
    size() { return state.length; }, 
  };
}

Невозможно мокнуть куски для тестирования.

Из-за "мокнутых кусков" тестирование превращается в ад. В данном случае, если очень хочется, можно создать фэйковый Queue и использовать его в тестах:


function someTest() {
  let calledTimes = 0;
  const fakeQueue = { enqueue(elem) { calledTimes += 1; } };

  objectUnderTest.someMethod(fakeQueue);

  assertEqual(1, calledTimes);
}

Конкретно этот пример надуманный, но суть он показывает.


При разростании это ведёт к такому

А зачем разрастаться, если объекты можно делать маленькими и разносить по разным файлам?


// LimitQueue.js
import Queue from './Queue';

export default function LimitQueue(limit) {
  const q = Queue();
  const self = {
    enqueue(elem) { q.size() < limit && q.enqueue(elem); }
  };
  return Object.setPrototypeOf(self, q);
}

// Queue.js
export default function Queue() {
  const state = [];
  return {
    enqueue(elem) { state.unshift(elem); },
    dequeue() { return state.pop(); },
    size() { return state.length; }, 
  };
}

Как думаете стоит ли написать полноценную статью с критикой такого подхода

Напишите, мне будет интересно почитать.


Но имхо это намеренная потеря гибкости не понятно зачем.

Лично мне такой подход дает больше гибкости. А вот классы обычно подталкивают к мышлению в рамках классов, код из-за этого наоборот теряет в гибкости.


JS прекрасный ОО-язык, но его зачем-то превращают в еще одну Java, а рынок, к сожалению, этот тренд поддерживает.

Вместо Object.setPrototypeOf(self, q) можно использовать Object.assign({}, q, self).

Есть люди, весьма не глупые люди замечу, которые уже давно утверждают «Favor object composition over class inheritance.» — The Gang of Four. А еще есть люди (Eric Elliott, Mattias Petter Johansson например), тоже кстати не глупые, которые уже в наше время достаточно хорошо и понятно на примерах разъясняют почему это утверждение верно и композиция ведет большей гибкости в разработке, а наследование ведет к «The Gorilla/banana problem»
Так нормальные классы не мешают композиции. Но это получается композиция скажем так открытая, можно подменить часть. А в module pattern это совсем закрыто. Инкапсуляция конечно хорошо, но не всегда.
InputField.prototype = Object.create(new Component());

Зачем так делать? Правильный вариант вот такой:


InputField.prototype = Object.create(Component.prototype);
Плюсую. Тоже обратил на это внимание, весьма странный подход использовать экземпляр объекта для «наследования» прототипа.
Sign up to leave a comment.