Статьи

Создание объектов JavaScript: шаблоны и лучшие практики

В этой статье я собираюсь познакомить вас с различными стилями создания объектов 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);

На самом деле, это тоже настолько распространенная модель, что язык имеет некоторую встроенную поддержку. Функция createnewcreatenew

 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 как можно лучше!