Чому літерали об'єкта в JavaScript - це круто?

13 хв. читання

До ECMAScript 2015 об'єктні літерали (або об'єктні ініціалізатори) в JavaScript були доволі елементарними. Можна було визначити 2 типи властивостей:

  • Пари назв властивостей і пов'язані з ними значення { name1: value1 }

  • Геттери { get name(){..} } і Сеттери { set name(val){..} } для обчислення значень властивостей.

На жаль, можливості об'єктного літералу - це всього один приклад:

var myObject = {  
  myString: 'value 1',
  get myNumber() {
    return this._myNumber;
  },
  set myNumber(value) {
    this._myNumber = Number(value);
  }
};
myObject.myString; // => 'value 1'  
myObject.myNumber = '15';  
myObject.myNumber; // => 15

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

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

На жаль, обмеження літералу не дозволяють домогтися цього з найпростішим рішенням. Ви мали використовувати Object.create() в поєднанні з об'єктним літералом для ініціалізації прототипу:

var myProto = {  
  propertyExists: function(name) {
    return name in this;    
  }
};
var myNumbers = Object.create(myProto);  
myNumbers['array'] = [1, 6, 7];  
myNumbers.propertyExists('array');      // => true  
myNumbers.propertyExists('collection'); // => false  

На мою думку, це незручне рішення. JavaScript є прототипно-орієнтований, то чому ж так багато болю при створенні об'єкту з прототипу?

На щастя, мова змінюється. Багато речей в JavaScript, які розчаровували, вирішуються крок за кроком.

У цій статті показано, як ES2015 вирішує описані вище проблеми і покращує літерал об'єкта з додатковими перевагами:

  • Налаштування прототипу на об'єктну конструкцію

  • Скорочені оголошення методів

  • Можливість здійснювати super виклики

  • Обчислювані імена властивостей

Крім того, давайте заглянемо в майбутнє і зустрінемо нові пропозиції (на етапі 2): залишкові і розширені властивості об'єкта.

es2015

1. Налаштування прототипу на об'єктну конструкцію

Як ви вже знаєте, один з варіантів, щоб отримати доступ до прототипу існуючого об'єкта застосувати геттер властивість proto:

var myObject = {  
  name: 'Hello World!'
};
myObject.__proto__;                         // => {}  
myObject.__proto__.isPrototypeOf(myObject); // => true  

myObject.proto повертає прототип об'єкту myObject.

Зверніть увагу, що використання object.__proto__ як геттер/сеттер не рекомендовано.

Замість цього, слід розглянути альтернативи Object.getPrototypeOf() і Object.setPrototypeOf().

Гарна частина полягає в тому, що ES2015 дозволяє використовувати літерал __proto__ як ім'я властивості для установки прототипу прямо в літералі об'єкту { __proto__: protoObject }.

Давайте використаємо властивість __proto__ для ініціалізації об'єкта, та поліпшення гіркої ситуації, описаної у вступі:

var myProto = {  
  propertyExists: function(name) {
    return name in this;    
  }
};
var myNumbers = {  
  __proto__: myProto,
  array: [1, 6, 7]
};
myNumbers.propertyExists('array');      // => true  
myNumbers.propertyExists('collection'); // => false  

myNumbers об'єкт створений з прототипом myProto з використанням спеціального імені властивості __proto__.

Об'єкт створюється одним оператором, без додаткових функцій, таких як Object.create().

Як бачите, кодування з __proto__ просте. Я завжди віддаю перевагу простим і очевидним рішенням.

Трохи відходячи від теми. Я вважаю дивним, що прості і гнучкі рішення вимагають великого обсягу роботи і дизайну. Якщо рішення є простим, вам може здатися, що проектувати його було легко. Проте, все навпаки:

  • Зробити його простим і зрозумілим – складно.
  • Зробити його складним і важко зрозумілим – легко.

Якщо щось виглядає занадто складним або не дуже зручним для використання, ймовірно, це не вважалося достатнім.

2.1 Особливі випадки використання __proto__

Навіть якщо __proto__ здається простим, є деякі конкретні сценарії, які ви повинні знати.

proto

Допускається використовувати __proto__ тільки один раз в літералі об'єкта. На дублювання JavaScript видає помилку:

var object = {  
  __proto__: {
    toString: function() {
      return '[object Numbers]'
    }
  },
  numbers: [1, 5, 89],
  __proto__: {
    toString: function() {
      return '[object ArrayOfNumbers]'
    }
  }
};

Літерал об'єкта в прикладі використовує два рази властивість __proto__, що не допускається. В цьому випадку, видається помилка SyntaxError: Duplicate proto fields are not allowed in object literals.

JavaScript дозволяє використовувати тільки об'єкт або null як значення властивості __proto__ . Будь-яка спроба використати примітивні типи (рядки, числа, булеві) або undefined просто ігнорується і не змінює прототип об'єкта.

Давайте розглянемо, це обмеження в якості прикладу:

var objUndefined = {  
  __proto__: undefined
};
Object.getPrototypeOf(objUndefined); // => {}  
var objNumber = {  
  __proto__: 15
};
Object.getPrototypeOf(objNumber);    // => {}  

Об'єктні літерали використовують undefined і число 15 для встановлення значення __proto__. Тому що тільки об'єкту або null дозволено бути прототипами, objUndefined і objNumber все ще мають свої прототипи за замовчуванням: прості об'єкти JavaScript {}. Значення __proto__ ігнорується.

Звичайно, було б дивно намагатися використовувати примітивні типи для встановлення прототипу об'єкта. Тож обмеження застосоване тут, є очікуваним.

2. Скорочене визначення методу

Можна використовувати більш короткий синтаксис для оголошення методів в літералах об'єктів, таким чином, щоб ключове слово function і двокрапка : були опущені. Це називається скорочене визначення методу.

Давайте визначимо деякі методи, використовуючи нову коротку форму:

var collection = {  
  items: [],
  add(item) {
    this.items.push(item);
  },
  get(index) {
    return this.items[index];
  }
};
collection.add(15);  
collection.add(3);  
collection.get(0); // => 15  

add() і get() методи, визначені в collection, використовуючи коротку форму. Перевага в тому, що методи, оголошені таким чином названі функціями, що зручно для процесу налагодження. Виконання collection.add.name з попереднього прикладу повертає ім'я функції 'add'.

3. Здійснювати super виклики

Цікавим удосконаленням є можливість використання ключового слова super як спосіб отримати доступ до успадкованих властивостей з ланцюжка прототипів. Погляньте на наступний приклад:

var calc = {  
  numbers: null,
  sumElements () {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  }
};
var numbers = {  
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements() {
    // Перевіряємо, що числа не є нульовими, або порожніми
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    } 
    return super.sumElements();
  }
};
numbers.sumElements(); // => 17  

calc є прототипом об'єкта numbers. У методі sumElements об'єкту numbers можна отримати доступ до методів з прототипу з використанням super ключового слова: super.sumElements().

Це метод корекції.

Зрештою super – ярлик для доступу до успадкованих властивостей від ланцюга прототипів об'єкта.

У попередньому прикладі ви можете спробувати викликати прототип безпосередньо виконуючи calc.sumElements(). Однак super.sumElements() є правильним варіантом, оскільки він отримує доступ до ланцюжку прототипів об'єкта. І гарантує, що метод sumElements() правильно звертається від прототипу до масиву з використанням this.numbers. Присутність super ясно показує, що успадковані властивості будуть використовувати.

3.1 super обмеження використання

super може бути використаний тільки всередині скороченого визначення методу в літералі об'єкта.

Якщо намагатися отримати до нього доступ зі звичайного оголошення { name: function() {} }, JavaScript видає помилку:

var calc = {  
  numbers: null,
  sumElements () {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  }
};
var numbers = {  
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements: function() {
    // Перевіряємо, що числа не є нульовими, або порожніми
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    } 
    return super.sumElements();
  }
};
// Видає SyntaxError: ключове слово 'super' тут недоречне
numbers.sumElements(); 

Метод sumElements визначається як властивість: sumElements: function() {...}. Оскільки super вимагає, щоб його використовували лише в середині скорочених методів, виклик його в такій ситуації видає SyntaxError: 'super' keyword unexpected here.

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

4. Обчислювальні імена властивостей

До ES2015, імена властивостей в ініціалізаторах об'єктів були літерали, в основному статичні рядки. Для того, щоб створити властивість з розрахунковим ім'ям, ви повинні були використовувати доступ до властивостей:

function prefix(prefStr, name) {  
   return prefStr + '_' + name;
}
var object = {};  
object[prefix('number', 'pi')] = 3.14;  
object[prefix('bool', 'false')] = false;  
object; // => { number_pi: 3.14, bool_false: false }  

Звичайно, цей спосіб визначити властивості, безумовно, приємний.

Обчислювання імені властивості вирішує проблему елегантно.

При оцінці імені властивості з виразу, помістіть код в квадратні дужки {[expression]: value}. Результатом оцінки виразу стає ім'я властивості.

Мені дуже подобається синтаксис: короткий і простий.

Давайте поліпшимо наведений вище приклад:

function prefix(prefStr, name) {  
   return prefStr + '_' + name;
}
var object = {  
  [prefix('number', 'pi')]: 3.14,
  [prefix('bool', 'false')]: false
};
object; // => { number_pi: 3.14, bool_false: false }  

[prefix('number', 'pi')] задає ім'я властивості шляхом оцінки виразу prefix('number', 'pi') , яким є 'number_pi'. Відповідно [prefix('bool', 'false')] задає друге ім'я властивості як 'bool_false'.

4.1 Symbol в якості імені властивості

Символи можуть також бути використані в якості обчислених імен властивостей. Просто переконайтеся, що включили їх в дужки: { [Symbol('name')]: 'Prop value' }.

Наприклад, давайте використаємо спеціальну властивість Symbol.iterator і перелічимо імена власних властивостей об'єкта. Перевірте наступний приклад:

var object = {  
   number1: 14,
   number2: 15,
   string1: 'hello',
   string2: 'world',
   [Symbol.iterator]: function *() {
     var own = Object.getOwnPropertyNames(this),
       prop;
     while(prop = own.pop()) {
       yield prop;
     }
   }
}
[...object]; // => ['number1', 'number2', 'string1', 'string2']

[Symbol.iterator]: function *() { } визначає властивість, яка використовується для перебору власних властивостей об'єкта. Оператор поширення [...object] використовує ітератор і повертає список власних властивостей.

5. Погляд у майбутнє: залишкові і розширені властивості

Залишкові і розширені властивості об'єктного літерала запропоновано в проекті (стадія 2), що робить їх кандидатами на нову версію JavaScript.

Вони являють собою еквівалент розширеного і залишкового операторів вже доступних для масивів в ECMAScript 2015.

Залишкові властивості дозволяють збирати властивості з об'єкта, які залишилися після деструктивного присвоєння. Наступний приклад збирає залишкові властивості після знищення object:

var object = {  
  propA: 1,
  propB: 2,
  propC: 3
};
let {propA, ...restObject} = object;  
propA;      // => 1  
restObject; // => { propB: 2, propC: 3 } 

Розширені властивості дозволяють скопіювати в літерал об'єкта власні властивості з вихідного об'єкта. У цьому прикладі літерал об'єкту збирає в object додаткові властивості з source об'єкта:

var source = {  
  propB: 2,
  propC: 3	
};
var object = {  
  propA: 1,
  ...source
}
object; // => { propA: 1, propB: 2, propC: 3 }  

6. На закінчення

JavaScript робить великі кроки.

Навіть відносно невелика конструкція, як літерал об'єкта стала значним поліпшення в ECMAScript 2015 і купа нових можливостей в запропонованому проекті.

Ви можете встановити прототип об'єкта безпосередньо з ініціалізації, використовуючи __proto__ ім'я властивості. Що легше, ніж мати справу з Object.create().

Зверніть увагу на те, що __proto__ є частиною програми B стандарту ES2015, використання яких не рекомендується. Ця реалізація програми потрібна для браузерів, але необов'язкова для інших середовищ. Проте NodeJS 4, 5 і 6 підтримують цю функцію.

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

Якщо ім'я властивості обчислюється під час виконання, тепер ви можете використовувати обчислювані імена властивостей [expression] для ініціалізації об'єктів.

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

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

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

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