Объекты в JS: создание, шаблоны и практические рекомендации
В этой статье я расскажу о различных способах, и том, позволяющих создать объекты в JavaScript чем они отличаются друг от друга.
Литералы объектов
В JavaScript объекты могут создаваться “из ничего“ - без класса, без шаблона, без прототипа.
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();
Фактически это общий шаблон, для которого JavaScript имеет встроенную поддержку. Нам не нужно создавать собственный общий объект (объект-прототип). Вместо этого объект-прототип создается автоматически вместе с каждой функцией, и мы можем добавить в него общие данные.
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();
Этот способ JavaScript создания объектов также имеет один недостаток. Он приводит к возникновению повторений. Первые и последние строки функции 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);
Это тоже общий шаблон, для которого JavaScript имеет встроенную поддержку. Функция 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 для обработки повторяющейся логики.
Но у этого способа JavaScript работы с объектами есть недостаток. Это громоздко и некрасиво, а реализации наследования еще более громоздки и некрасивы.
Классы ES6
Это относительно недавнее дополнение к JavaScript, которое предлагает значительно более чистый синтаксис, чтобы сделать то же самое.
class Thing {
constructor() {
this.x = 42;
this.y = 3.14;
}
f() {}
g() {}
}
var o = new Thing();
Сравнение
На протяжении многих лет мы использовали в JavaScript внутренние и внешние связи с цепочками прототипов. На сегодняшний день два наиболее распространенных метода создания JavaScript объектов - это синтаксис классов, который в значительной мере опирается на цепочки прототипов. А также фабричные функции, которые обычно не использует цепочки прототипов вообще. Эти два метода незначительно отличаются друг от друга в плане производительности и функциональности.
Представление
JavaScript на сегодняшний день настолько оптимизирован, что уже почти невозможно просмотреть код и решить, что будет быстрее. Решающее значение имеют фактические замеры показателей. Тем не менее, иногда даже они могут подвести нас.
Обновления движка JavaScript выходят каждые шесть недель, иногда со значительными изменениями в производительности. Также часто все замеры, которые мы ранее производили, и все решения, которые принимали на основе этих измерений, больше не являются верными.
Для себя я решил использовать наиболее близкий к официальному и наиболее широко используемый синтаксис, предполагая, что он будет давать наибольшую производительность. На сегодняшний день - это синтаксис классов. В тот момент, когда я пишу эти строки, синтаксис классов примерно в три раза быстрее, чем фабричные функции.
Функционал
В связи с вводом ES6 существуют незначительные различия в функционале между классами и фабричными функциями. На сегодняшний день и те, и другие могут обеспечивать приватность данных: фабричные функции с помощью замыканий, а классы – с помощью WeakMap.
Также с их помощью можно создавать множественные наследования: с помощью фабричных функций путем смешивания других свойств в свой собственный объект, а с помощью классов - путем смешивания других свойств в его прототип. И фабричные функции, и классы могут возвращать любой произвольный объект, если это необходимо. И оба метода JavaScript создания объектов предлагают простой синтаксис.
Вывод
Учитывая все сказанное выше, мое предпочтение - синтаксис классов. Это стандартный, простой и чистый способ.