Створення об'єктів в JavaScript: патерни та кращі практики

5 хв. читання
05 листопада 2020

Створення об'єктів в JavaScript - доволі заплутане питання. Є декілька способів зробити це. Новачки (а інколи і досвідчені користувачі) часто в них плутаються. Але не лякайтеся - це не складний процес, і в цій статті ми розберемося в різних стилях створення об'єктів.

Літерали

Першою зупинкою в сьогоднішній подорожі будуть літерали. Це найлегший спосіб створити об'єкт. JavaScript дозволяє створювати об'єкти без використання класів, шаблонів, прототипів — слід лише вказати змінні та методи.

var o = {
  x: 42,
  y: 3.14,
  f: function() {},
  g: function() {}
};

Але тут є свої недоліки. Якщо нам потрібно створити такий самий об'єкт в іншому місці, то прийдеться копіювати весь код. Нам потрібен спосіб створювати не один об'єкт, а цілу "родину".

Функції-фабрики

Наступною зупинкою будуть функції-фабрики. Це найпростіший спосіб створити декілька схожих об'єктів. Замість того, щоб використовувати літерал напряму, ми створюємо об'єкт в функції і повертаємо його. Тепер, коли нам треба створити декілька однакових об'єктів, слід просто викликати функцію.

function thing() {
  return {
    x: 42,
    y: 3.14,
    f: function() {},
    g: function() {}
  };
}

var o = thing();

Але й тут є мінуси. Такий спосіб може викликати проблеми з пам'яттю, адже кожен об'єкт зберігає власну копію кожної функції. А нам би хотілося, щоб кожен об'єкт використовував лише одну функцію.

Прототипи

JavaScript має вбудований механізм для розподілу даних між об'єктами, що називається ланцюжком прототипів (prototype chain). Коли ми запитуємо якісь дані у об'єкт, він може задовольнити наш запит делегуванням до іншого об'єкта. Ми можемо використати це для того, щоб змінити нашу функцію-фабрику так, щоб лише дані були унікальними, а методи — ні.

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();

Але і тут не обійшлось без мінусів. Такий спосіб веде до повторення. Перша та остання стрічки функції 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, і її можна замінити.

Thing.prototype.f = function() {};
Thing.prototype.g = function() {};

function Thing() {
  this.x = 42;
  this.y = 3.14;
}

var o = new Thing();

Тепер ми дійшли до того, що називають ES5-класами. Вони є функціями, які створюють об'єкти, делегують спільно використовувані дані до об'єкта-прототипу і вносять ключове слово new, щоб позбутися повторювання коду.

Мінус в тому, що це некрасиво та багатослівно. А організацію наслідування вам взагалі краще не бачити, там все стає ще гірше.

Класи в ES6

Одним з недавніх доповнень до стандарту ES6 є новий стиль для класів, що привносить в JavaScript зручний та лаконічний спосіб створення об'єктів.

class Thing {
  constructor() {
    this.x = 42;
    this.y = 3.14;
  }

  f() {}
  g() {}
}

var o = new Thing();

Порівняння

З роками досвіду ви будете використовувати в основному лише два способи: класи та функції-фабрики. Ці два стилі відрізняються в зовнішньому вигляді, але в продуктивності та швидкості вони майже ідентичні.

Продуктивність

Зараз рушії JavaScript дуже сильно оптимізовані, і тепер відразу важко поглянути на код і визначити, що буде гальмувати виконання. Вимірювання критичні. Проте, іноді навіть вони можуть підвести вас. Зазвичай рушій JavaScript оновлюється раз на шість тижнів, іноді зі значними змінами в продуктивності. І тепер всі наші вимірювання та рішення, прийняті на їх основі стають не актуальними. Тому я віддаю перевагу офіційному та найбільш популярному синтаксису, в надії, що саме він буде отримувати найкращу підтримку. На даний момент це класи, і зараз вони працюють втричі швидше функцій-фабрик, що повертають літерал.

Фічі

Декілька відмінностей між фабриками та класами з'явилося в ES6. Сьогодні обидва способи підтримують приватні дані - функції через замикання, а класи через weak maps. Обидва способи підтримують множинне наслідування, можуть повертати довільний об'єкт і обидва синтаксиса є простими і лаконічними.

Висновок

Дивлячись на все це, я віддаю перевагу класам. Вони стандартні, вони прості, вони швидкі та не уступають по можливостям функціям-фабрикам.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.2K
Приєднався: 9 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація