Комментарии 17
Уважаемые читатели! Пользуетесь ли вы синтаксическими конструкциями ES6 для работы с классами в JavaScript?
Конечно если сидеть на одном JS и не планировать никогда в жизни другие языки (и не использовать другие языки параллельно), тогда можно привыкнуть к его прототипам, и прочее. Но если я в основном пишу на Java-Python-PHP-Ruby-TypeScript-...-ANY_Language, то зачем переключать мозги в JS, когда я могу использовать те же самые конструкции, что и в других языках?
Поэтому, если я могу создать класс, как и в других языках, и работать с ним точно так же, а компиляторы за меня переведут все это дело в более мутный (для меня) JS, то конечно.
Аналогичная опция есть и в Babel: http://babeljs.io/docs/en/babel-plugin-transform-runtime
Оптимизация такого уровня требует учёта мельчайших деталей экосистемы JavaScript, влияющих на производительность, в частности, учёта особенностей того, как устроены классы и механизмы наследования в ES6.
кстати, для тех кому важна производительность, не стоит сразу использовать все новые плюшки языка: fhinkel.rocks/six-speed
Предупреждение: Изменение прототипа [[Prototype]] объекта является, по самой природе оптимизации доступа к свойствам в современных движках JavaScript, очень медленной операцией, это справедливо для любого браузера и движка JavaScript. Изменение прототипов очень тонко и обширно влияет на производительность, причём это влияние не ограничивается просто временем, проведённым внутри метода Object.setPrototypeOf(), оно может распространяться на любой код, который имеет доступ к любому объекту, чей прототип [[Prototype]] был изменён. Если вы заботитесь о производительности, вы никогда не должны изменять прототип [[Prototype]] объекта. Вместо этого создайте объект с нужным прототипом [[Prototype]], с помощью метода Object.create().и TypeScript, и Babel используют его. Как с этим обстоят дела?
Я не обращал внимания, может в спецификации указано явно, что новые конструкции являются эквивалентом старых. Надо посмотреть. Если нет, то это никакой не синтаксичский сахар.
Когда мы размышляем об объектах, то первое, что приходит в голову — это классы.
Видимо, зря придумали ключевое слово class
, оно затмевает сознание и мешает думать об объектах. В JS можно создавать объекты вообще без this
, new
и class
, а только с помощью фабрик, замыканий и, собственно, объектов:
function Queue() {
const store = [];
return {
enqueue(elem) { store.unshift(elem); },
dequeue() { return store.pop(); },
}
}
Чем такой способ создания объектов хуже классов?
Не хуже, тут вопрос стандартизации. Если в большинстве языков объекты задаются через class Name {}
и вызываются через new Name();
, то логичней использовать именно эту структуру, а не то, что Вы написали выше.
Невозможно наследовать. Невозможно мокнуть куски для тестирования. Невозможность распилить код на несколько файлов в таком виде. При разростании это ведёт к такому 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, а рынок, к сожалению, этот тренд поддерживает.
InputField.prototype = Object.create(new Component());
Зачем так делать? Правильный вариант вот такой:
InputField.prototype = Object.create(Component.prototype);
Как работает JS: классы и наследование, транспиляция в Babel и TypeScript