До 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): залишкові і розширені властивості об'єкта.
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__
тільки один раз в літералі об'єкта. На дублювання 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]
для ініціалізації об'єктів.
Ще немає коментарів