В этой статье я собираюсь познакомить вас с различными стилями создания объектов JavaScript и с тем, как каждый из них строится на других поэтапно.
JavaScript имеет множество стилей для создания объектов, и новички и ветераны могут чувствовать себя ошеломленными из-за выбора и неуверенности в том, что им следует использовать. Но, несмотря на разнообразие и разный синтаксис каждого из них, они более похожи, чем вы, возможно, думаете.
Объектные литералы
Первая остановка в нашем туре — это самый простой способ создания объекта JavaScript — литерал объекта. JavaScript заявляет, что объекты могут быть созданы «ex nilo» из ничего — ни класса, ни шаблона, ни прототипа — просто пуф! объект с методами и данными:
var o = {
x: 42,
y: 3.14,
f: function() {},
g: function() {}
};
Но есть и недостаток. Если нам нужно создать такой же тип объекта в других местах, то мы в конечном итоге вставим копирование методов, данных и инициализации объекта. Нам нужен способ создать не только один объект, но и семейство объектов.
Фабрика Функции
Следующая остановка в нашем туре по созданию объектов JavaScript — фабричная функция. Это самый простой способ создать семейство объектов, которые имеют одинаковую структуру, интерфейс и реализацию. Вместо того, чтобы создавать литерал объекта напрямую, мы возвращаем литерал объекта из функции. Таким образом, если нам нужно создать один и тот же тип объекта несколько раз или в нескольких местах, нам нужно только вызвать функцию:
function thing() {
return {
x: 42,
y: 3.14,
f: function() {},
g: function() {}
};
}
var o = thing();
Но есть и недостаток. Такой подход к созданию объекта JavaScript может вызвать переполнение памяти, поскольку каждый объект содержит свою уникальную копию каждой функции. В идеале мы хотим, чтобы каждый объект разделял только одну копию своих функций.
Прототип Цепи
JavaScript предоставляет нам встроенный механизм для обмена данными между объектами, называемый цепочкой прототипов . Когда мы получаем доступ к свойству объекта, он может выполнить этот запрос, передав его другому объекту. Мы можем использовать это и изменить нашу фабричную функцию так, чтобы каждый создаваемый ею объект содержал только данные, уникальные для этого конкретного объекта, и делегировал все другие запросы свойств в один общий объект:
var thingPrototype = {
f: function() {},
g: function() {}
};
function thing() {
var o = Object.create(thingPrototype);
o.x = 42;
o.y = 3.14;
return o;
}
var o = thing();
Фактически, это настолько распространенная модель, что язык имеет встроенную поддержку для него. Нам не нужно создавать наш собственный общий объект (объект-прототип). Вместо этого объект-прототип создается для нас автоматически вместе с каждой функцией, и мы можем поместить наши общие данные туда:
thing.prototype.f = function() {};
thing.prototype.g = function() {};
function thing() {
var o = Object.create(thing.prototype);
o.x = 42;
o.y = 3.14;
return o;
}
var o = thing();
Но есть и недостаток. Это приведет к некоторому повторению. Первые и последние строки функции thing
ES5 Классы
Мы можем изолировать повторяющиеся линии, переместив их в их собственную функцию. Эта функция создаст объект, который делегирует прототип какой-то другой произвольной функции, затем вызовет эту функцию с вновь созданным объектом в качестве аргумента и, наконец, вернет объект:
function create(fn) {
var o = Object.create(fn.prototype);
fn.call(o);
return o;
}
// ...
Thing.prototype.f = function() {};
Thing.prototype.g = function() {};
function Thing() {
this.x = 42;
this.y = 3.14;
}
var o = create(Thing);
На самом деле, это тоже настолько распространенная модель, что язык имеет некоторую встроенную поддержку. Функция create
new
create
new
Thing.prototype.f = function() {};
Thing.prototype.g = function() {};
function Thing() {
this.x = 42;
this.y = 3.14;
}
var o = new Thing();
Теперь мы пришли к тому, что мы обычно называем «классами ES5». Это функции создания объектов, которые делегируют общие данные объекту-прототипу и полагаются на new
Но есть и недостаток. Это многословно и безобразно, а реализация наследования еще более многословна и безобразна.
ES6 Классы
Относительно недавним дополнением к JavaScript являются классы ES6, которые предлагают значительно более чистый синтаксис для того же:
class Thing {
constructor() {
this.x = 42;
this.y = 3.14;
}
f() {}
g() {}
}
const o = new Thing();
сравнение
На протяжении многих лет мы, JavaScripters, имели отношения время от времени с цепочкой прототипов, и сегодня два наиболее распространенных стиля, с которыми вы можете столкнуться, — это синтаксис класса, который в значительной степени зависит от цепочки прототипов, и функция фабрики. синтаксис, который обычно вообще не зависит от цепочки прототипов. Два стиля отличаются — но незначительно — по производительности и функциональности.
Производительность
Сегодня движки JavaScript настолько сильно оптимизированы, что практически невозможно взглянуть на наш код и понять, что будет быстрее. Измерение имеет решающее значение. Тем не менее, иногда даже измерения могут подвести нас. Как правило, обновленный движок JavaScript выпускается каждые шесть недель, иногда со значительными изменениями в производительности, и любые измерения, которые мы ранее предприняли, и любые решения, которые мы приняли на основе этих измерений, выходят прямо в окно. Итак, мое эмпирическое правило состояло в том, чтобы отдавать предпочтение наиболее официальному и наиболее широко используемому синтаксису при условии, что он будет подвергаться наиболее тщательному анализу и будет наиболее продуктивным большую часть времени . Прямо сейчас это синтаксис класса, и когда я пишу это, синтаксис класса примерно в 3 раза быстрее, чем фабричная функция, возвращающая литерал.
Характеристики
Каких различий между классами и фабричными функциями испарилось с ES6. Сегодня как фабричные функции, так и классы могут применять по-настоящему частные данные — фабричные функции с замыканиями и классы со слабыми отображениями. Оба могут достигать функций фабрики множественного наследования, смешивая другие свойства в свои собственные объекты, а классы также смешивая другие свойства в их прототипе, или с фабриками классов, или с прокси. И фабричные функции, и классы могут возвращать любой произвольный объект, если это необходимо. И оба предлагают простой синтаксис.
Вывод
Учитывая все вышесказанное, я предпочитаю создавать объекты JavaScript, используя синтаксис класса. Он стандартный, простой и чистый, быстрый и предоставляет все функции, которые когда-то могли предоставить только фабрики.
Эта статья была рецензирована Тимом Севериеном и Себастьяном Зейтцем . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!