Розширення функціональності об’єктів
ECMAScript 6 значною мірою спрямований на поліпшення функціональності об’єктів, що має зміст, адже майже будь–яке значення в JavaScript є певною мірою об’єктами. Крім того, кількість об’єктів, що використовують в пересічних JavaScript–програмах продовжує зростати разом зі збільшенням складності JavaScript–додатків, а це означає, що програми постійно створюють все більше і більше об’єктів. Зі збільшенням кількості об’єктів з’являється і необхідність використовувати їх більш ефективно.
ECMAScript 6 покращує ряд властивостей об’єктів: від простого розширення синтаксису до опцій для їх маніпулювання та взаємодії.
Категорії об’єктів
JavaScript використовує змішану термінологію для опису об’єктів, які знаходяться у стандарті, на відміну від об’єктів у різноманітних оточеннях, як от у браузерах чи Node.js. Специфікація ECMAScript 6 має внести чітке визначення для кожної з категорій об’єктів. Важливо зрозуміти цю термінологію, щоб мати хороше розуміння мови в цілому. Об’єкти бувають таких категорій:
- Звичайні об’єкти — мають повну поведінку за замовчуванням для об’єктів у JavaScript.
- Незвичайні об’єкти — мають внутрішню поведінку, яка певним чином відрізняється від поведінки за замовчуванням.
- Стандартні об’єкти — об’єкти визначені у ECMAScript 6, такі як
Array
,Date
і так далі. Стандартні об’єкти можуть бути звичайними або незвичайними. - Вбудовані об’єкти — об’єкти, що присутні у середовищі виконання JavaScript, коли скрипт починає виконуватись. Всі стандартні об’єкти є вбудованими об’єктами.
У книзі я користуватимусь цією термінологією для пояснення різноманітних об’єктів, визначених у ECMAScript 6.
Розширення синтаксису об’єктного літерала
Об’єктний літерал є одним з найбільш популярних патернів у JavaScript. JSON побудований на його синтаксисі, і він є майже у будь—якому JavaScript–файлі в Інтернеті. Об’єктний літерал став таким популярним завдяки своєму лаконічному синтаксису для створення об’єктів, що в іншому випадку могло б зайняти кілька рядків коду. На щастя розробників, ECMAScript 6 робить об’єктні літерали більш потужними та навіть більш лаконічними завдяки розширенню синтаксису кількома способами.
Скорочення ініціалізації властивостей
У ECMAScript 5 та раніше, літерали об’єктів були простими колекціями пар "ім'я-значення". Це означало, що могли виникати дублювання тоді, коли значення властивостей вже ініціалізовані. До прикладу:
function createPerson(name, age) {
return {
name: name,
age: age
};
}
Функція createPerson()
створює об’єкт у якого властивість name така ж сама, як і параметр name у функції. У результаті це виглядає як дублювання name
та age
, хоча й один name є властивістю об’єкта, а інший встановлює значення цій властивості. Ключеві name
у об’єкті, що повернеться, буде присвоєно значення, яке міститься у змінній name
, а ключеві age
, у об’єкті, що повернеться, буде присвоєно значення, що зберігається у змінній age
.
У ECMAScript 6 ви можете позбутись такого дублювання завдяки скороченню ініціалізації властивостей. Коли ім’я властивості таке ж, як і ім’я локальної змінної, ви можете просто написати ім’я без двокрапки та значення. До прикладу, createPerson()
може бути переписана на ECMAScript 6 ось так:
function createPerson(name, age) {
return {
name,
age
};
}
Коли властивість у об’єктному літералі має лише ім’я, рушій JavaScript шукає у суміжній області видимості змінну з таким самим ім’ям. Якщо він знаходить таку, значення цієї змінної присвоюється такому ж імені у об’єктному літералі.У цьому прикладі, властивості name
літералу об’єкта присвоюється значення локальної змінної name
.
Таке розширення робить ініціалізацію об’єктних літералів навіть більш лаконічною та допомагає скоротити кількість помилок, пов’язаних з іменуванням. Присвоєння властивості локальної змінної з таким же самим ім’ям є дуже поширеним патерном у JavaScript, що робить це нововведення дуже бажаним.
Лаконічні методи
ECMAScript 6 також вдосконалює синтаксис присвоєння методів у об’єктних літералах. У ECMAScript 5 та раніше, щоб додати об’єкту метод, ви мусили задавати ім’я, а тоді повне визначення функції, ось так:
var person = {
name: "Nicholas",
sayName: function() {
console.log(this.name);
}
};
У ECMAScript 6 синтаксис зроблено більше лаконічним, щоб позбутись двокрапки та ключового слова function
. Це означає, що ви можете переписати попередній приклад так:
var person = {
name: "Nicholas",
sayName() {
console.log(this.name);
}
};
Такий скорочений синтаксис, який також називають синтаксисом лаконічних методів, створює метод об’єкту person
точно так само, як і у попередньому прикладі. Властивості sayName()
присвоюється анонімна функція, яка має точно такі ж характеристики, як і функція sayName()
на ECMAScript 5. Єдина відмінність у тому, що лаконічні методи можуть використовувати super
(йтиметься пізніше у розділі «Легкий доступ до прототипу через посилання super section), тоді як нелаконічні методи не можуть.
I> Властивість name
методу, що створюється з допомогою цього скорочення, є ім’ям, яке вказане перед круглими дужками. У останньому прикладі, властивістю name
для person.sayName()
буде "sayName"
.
Обчислювані імена властивостей
У ECMAScript 5 та раніше можна було обчислювати імена властивостей об’єкта, встановлюючи їх з допомогою квадратних дужок замість використання крапкового запису. Квадратні дужки дозволяють вам задавати імена властивостей використовуючи змінні та рядкові літерали, що можуть містити символи, які би спричинили синтаксичну помилку, якби використовувались у якості ідентифікаторів. Ось приклад:
var person = {},
lastName = "last name";
person["first name"] = "Nicholas";
person[lastName] = "Zakas";
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
Оскільки lastName
присвоєно значення "last name"
, обидві властивості у цьому прикладі містять пробіли, роблячи неможливим звернення до них з допомогою крапкового запису. Однак, квадратні дужки дозволяють використовувати будь–який рядок у якості ім’я властивості, тому присвоєння "first name"
значення "Nicholas"
та "last name"
значення "Zakas"
працює.
Окрім того, ви можете використовувати рядкові літерали, безпосередньо, в якості імен властивостей об’єктних літералів, як тут:
var person = {
"first name": "Nicholas"
};
console.log(person["first name"]); // "Nicholas"
Цей патерн працює для імен властивостей, що відомі наперед і можуть бути представлені у вигляді рядкового літералу. Однак, якщо б ім'я властивості "first name"
зберігалося у змінній (як у попередньому прикладі) або обчислювалось, тоді неможливо було б задати цю властивість з допомогою об’єктного літералу у ECMAScript 5.
У ECMAScript 6 обчислювані імена властивостей є частиною синтаксису об’єктного літералу, і вони можуть використовуватись з допомогою тих самих квадратних дужок, що використовувались для посилання на імена властивостей у екземплярах об’єктів. Наприклад:
var lastName = "last name";
var person = {
"first name": "Nicholas",
[lastName]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
Квадратні дужки всередині об’єктного літералу показують, що ім’я властивості має бути обчислюваним, тому його вміст обчислюється як рядок. Це означає, що ви можете також використовувати вирази ось так:
var suffix = " name";
var person = {
["first" + suffix]: "Nicholas",
["last" + suffix]: "Zakas"
};
console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"
Результатом обчислення властивостей будуть "first name"
та "last name"
, і ці рядки можуть використовуватись для звернення до властивостей згодом. Будь–що, що ви могли би вкласти до квадратних дужок у записі з екземплярами об’єктів, також працюватиме для обчислюваних властивостей всередині об’єктних літералів.
Нові методи
Однією з цілей ECMAScript, починаючи з ECMAScript 5, було уникання створення нових глобальних функцій та методів у Object.prototype
, а замість цього спробувати знайти об’єкти, у яких нові методи повинні бути доступними. У результаті, Глобальний Object
отримав більшу кількість методів, у той час як усі інші об’єкти — ні. ECMAScript 6 вводить пару нових методів у глобального Object
, які розроблені для спрощення певних завдань.
Метод Object.is()
Коли ви хочете порівняти два значення у JavaScript, скоріш за все, ви звикли використовувати оператор рівності (==
) або оператор ідентичної рівності (===
). Багато розробників надають перевагу останньому, щоб уникнути примусового зведення типів. Проте навіть оператор ідентичної рівності є не зовсім точним. Наприклад, значення +0 та -0 розглядаються оператором ===
рівними, хоча рушії JavaScript описують їх як різні. Також NaN === NaN
повертає false
, що призводить до необхідності використовувати isNaN()
, щоб перевірити NaN
точно.
ECMAScript 6 вводить метод Object.is()
, щоб компенсувати недоліки оператора ідентичної рівності. Цей метод приймає два аргументи та повертає true
, якщо значення є еквівалентними. Два значення вважаються еквівалентними тоді, коли вони є мають однакові типи та значення. Ось кілька прикладів:
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(5 == 5); // true
console.log(5 == "5"); // true
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false
У багатьох випадках, Object.is()
працює так само як і оператор ===
. Єдина відмінність полягає у тому, що +0 та -0 розглядаються як нееквівалентні та NaN
вважається еквівалентним до NaN
. Проте немає потреби припиняти використовувати оператори рівності зовсім. Визначайте коли користуватись Object.is()
замість ==
або ===
, виходячи з того, як має поводитись ваш код.
Метод Object.assign()
Домішки (mixins) є одним з найпопулярніших патернів для композиції об’єктів у JavaScript. У домішці один об’єкт отримує властивості та методи від іншого. Багато JavaScript–бібліотек мають метод mixin схожий на цей:
function mixin(receiver, supplier) {
Object.keys(supplier).forEach(function(key) {
receiver[key] = supplier[key];
});
return receiver;
}
Функція mixin()
ітерується по власних властивостях supplier
та копіює їх до receiver
(неповну копію, у якій посилання на об’єкти є загальними, тоді як значення властивостей є об’єктами). Це дозволяє receiver
отримувати нові властивості без наслідування, як у цьому прикладі:
function EventTarget() { /*...*/ }
EventTarget.prototype = {
constructor: EventTarget,
emit: function() { /*...*/ },
on: function() { /*...*/ }
};
var myObject = {};
mixin(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");
Тут myObject
отримує поведінку з об’єкта EventTarget.prototype
. Це дає myObject
можливість публікувати події та підписуватись на них з допомогою методів emit()
та on()
відповідно.
Цей патерн став досить популярним, тому ECMAScript 6 додає метод Object.assign()
, який поводиться так само. Зміна імені з mixin()
на assign()
відображає дійсну операцію, що відбувається. Оскільки метод mixin()
використовує оператор присвоєння (=
), він не зможе скопіювати властивість-аксесор приймача у вигляді властивості–аксесора. Ім’я Object.assign()
було обране щоб відображати цю відмінність. Однак, зауважте, що Object.assign()
не копіює властивості, ключі яких є символами, які я опишу у Главі 6.
I> Схожі методи у різних бібліотеках можуть мати різні імена для одного і того ж функціоналу: популярними альтернативами є включення методів extend()
та mix()
. У ECMAScript 6 також був метод Object.mixin()
на додачу до методу Object.assign()
. Основною відмінністю було те, що Object.mixin()
копіює ще й властивості-аксесори, але цей метод було виключено через можливість super
(описано у розділі "Легкий доступ до прототипу через посилання super" цієї глави).
Ви можете використовувати Object.assign()
усюди де, функція mixin()
могла би використовуватись. Ось приклад:
function EventTarget() { /*...*/ }
EventTarget.prototype = {
constructor: EventTarget,
emit: function() { /*...*/ },
on: function() { /*...*/ }
}
var myObject = {}
Object.assign(myObject, EventTarget.prototype);
myObject.emit("somethingChanged");
Метод Object.assign()
приймає будь–яку кількість віддавачів властивостей. Приймач отримуватиме властивості у тому порядку, в якому віддавачі були вказані. Це означає, що другий віддавач може перезаписати значення з першого віддавача в отримувачі, що й ілюструє цей приклад:
var receiver = {};
Object.assign(receiver,
{
type: "js",
name: "file.js"
},
{
type: "css"
}
);
console.log(receiver.type); // "css"
console.log(receiver.name); // "file.js"
Значенням receiver.type
буде "css"
оскільки другий віддавач перезаписав значення першого.
Метод Object.assign()
не є великим доповненням до ECMAScript 6, проте він формалізує загальну функцію, яка надається багатьма JavaScript–бібліотеками.
A> Робота з властивостями–аксесорами
A> Запам’ятайте, якщо віддавач має властивості–аксесори, то Object.assign()
не створить їх у отримувачі. Оскільки Object.assign()
використовує оператор присвоєння, властивість аксесора у віддавачі стане полем даних у отримувачі. Наприклад:
var receiver = {},
supplier = {
get name() {
return "file.js"
}
};
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value); // "file.js"
console.log(descriptor.get); // undefined
A> У цьому прикладі, supplier
має властивість–аксесор під назвою name
. Після використання методу Object.assign()
, властивість receiver.name
буде полем даних зі значенням "file.js"
, тому що supplier.name
поверне "file.js"
, коли Object.assign()
буде викликано.
Дублювання властивостей у об’єктних літералах
Строгий режим у ECMAScript 5 вводив перевірку дублювання властивостей у об’єктних літералах, що провокувала помилку, якщо відбувалось дублювання. Наприклад, цей код спричинив би помилку:
"use strict";
var person = {
name: "Nicholas",
name: "Greg" // синтаксична помилка у строгому режимі ES5
};
При запуску у строгому режимі ECMAScript 5, друга властивість name
призведе до синтаксичної помилки. Проте у ECMAScript 6, перевірка дублювання властивостей була видалена. Як у строгому так і в нестрогому режимах код більше не перевіряється на дублювання властивостей. Замість цього, значення останньої властивості з даним ім’ям стане актуальним значенням, як і показано тут:
"use strict";
var person = {
name: "Nicholas",
name: "Greg" // ніякої помилки у строгому режимі ES6
};
console.log(person.name); // "Greg"
У цьому прикладі, значенням person.name
буде is "Greg"
тому, що це останнє значення присвоєне цій властивості.
Власний порядок перелічення властивостей
ECMAScript 5 не задає порядку перелічення властивостей об’єкту — це покладалось на постачальників JavaScript–рушіїв. Однак ECMAScript 6 строго задає порядок у якому власні властивості мають повертатись, коли вони перераховані. Це впливає на те, як властивості повертаються з Object.getOwnPropertyNames()
та Reflect.ownKeys
(описано у Главі 12). Це також впливає на те, як властивості обробляються у Object.assign()
.
Базовий порядок переліку власних властивостей такий:
- Всі нумеровані ключі у порядку зростання.
- Всі рядкові ключі у порядку, в якому вони були додані до об’єкту.
- Всі символьні ключі (описано у Главі 6) у порядку, в якому вони були додані до об’єкту.
Ось приклад:
var obj = {
a: 1,
0: 1,
c: 1,
2: 1,
b: 1,
1: 1
};
obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join("")); // "012acbd"
Метод Object.getOwnPropertyNames()
повертає властивості obj
у порядку: 0
, 1
, 2
, a
, c
, b
, d
. Зауважте, що нумеровані ключі згруповані та відсортовані, хоча в об’єктному літералі вони записані у довільному порядку. Рядкові ключі ідуть після нумерованих та в порядку, в якому їх було додано до obj
. Ключі задані у об’єктному літералі йдуть попереду, за ними слідують динамічні ключі, що були додані пізніше (у цьому випадку d
).
W> Цикл for-in
продовжує мати незаданий порядок перелічення, оскільки не всі JavaScript–рушії реалізують його однаково. Методи Object.keys() та JSON.stringify() мають однаковий (невказаний) порядок перелічення, як і for-in
.
Порядок перелічення є маленькою зміною у тому як працює JavaScript, адже досить важко знайти програму, коректна робота якої залежить від конкретного порядку перелічення. ECMAScript 6, встановленням порядку перелічення, дозволяє бути певними, що JavaScript–код, який залежить від порядку переліку буде працювати коректно, незалежно від того, де він виконується.
Більш потужні прототипи
Прототипи є фундаментом наслідування у JavaScript, а ECMAScript 6 продовжує робити прототипи більш потужними. Попередні версії JavaScript мають невелику свободу дій над прототипами. Однак зі становленням мови та тим, що розробники починали краще розуміти як працюють прототипи, стало зрозуміло, що розробники бажають більшого контролю над прототипами та легших способів роботи з ними. Як результат, ECMAScript 6 вводить кілька покращень прототипів.
Зміна прототипу об’єкта
Зазвичай прототип об’єкту задається під час створення об’єкту, або через конструктор, або через метод Object.create()
. Ідея, що прототип об’єкту залишається незмінним після ініціалізації була однією з найбільших допущень програмування JavaScript на ECMAScript 5. ECMAScript 5 додав метод Object.getPrototypeOf()
для отримання прототипу будь–якого переданого об’єкту, проте не вистачало способу зміни прототипу об’єкта після ініціалізації.
ECMAScript 6 змінює це допущення шляхом введення методу Object.setPrototypeOf()
, який дозволяє змінювати прототип будь–якого переданого об’єкту. Метод Object.setPrototypeOf()
приймає два аргументи: об’єкт який потрібно змінити та об’єкт, що стане його прототипом. До прикладу:
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
// person — це прототип
let friend = Object.create(person);
console.log(friend.getGreeting()); // "Hello"
console.log(Object.getPrototypeOf(friend) === person); // true
// встановлюємо прототип dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof"
console.log(Object.getPrototypeOf(friend) === dog); // true
Цей код задає два базових об’єкти: person
та dog
. Обидва мають метод getGreeting()
, що повертає рядок. Об’єкт friend
спершу наслідується від об’єкту person
, тому getGreeting()
виводить "Hello"
. Коли прототипом стає об’єкт dog
, fiend.getGreeting()
виводить "Woof"
тому, що початкове відношення з person
було розірване.
Дійсне значення прототипу об’єкту зберігається у суто внутрішній властивості під назвою [[Prototype]]
. Метод Object.getPrototypeOf()
повертає значення, що зберігається у [[Prototype]]
, а Object.setPrototypeOf()
змінює значення, що зберігається у [[Prototype]]
. Однак, це не єдиний спосіб взаємодії зі значенням [[Prototype]]
.
Легкий доступ до прототипу через посилання super
Як вже згадувалось раніше, прототипи дуже важливі для JavaScript і у ECMAScript 6 було зроблено багато для того, щоб зробити їх використання легшим. Ще одним покращенням є введення посилання super
, яке робить легшим доступ до функціоналу прототипу. Наприклад, щоб перезаписати метод у екземпляра об’єкта, що також викликає метод прототипу з таким самим ім’ям, на ECMAScript 5 ви зробили б щось таке:
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
}
};
// встановлюємо прототип person
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(Object.getPrototypeOf(friend) === person); // true
// встановлюємо прототип dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof, hi!"
console.log(Object.getPrototypeOf(friend) === dog); // true
У цьому прикладі, getGreeting()
у friend
викликає метод прототипа з таким самим ім’ям. Метод Object.getPrototypeOf()
перевіряє чи викликається певний прототип, а тоді додатковий рядок додається до виводу. Додатковий .call(this)
перевіряє чи значення this
всередині прототипу встановлено коректно.
Не забувати про використання Object.getPrototypeOf()
та .call(this)
для виклику методу прототипу — це дещо складно, тому ECMAScript 6 вводить super
. Простота полягає у тому, що super
— це вказівник на поточний прототип об’єкту, фактично значення Object.getPrototypeOf(this)
. Знаючи це, ви можете спростити метод getGreeting()
ось так:
let friend = {
getGreeting() {
// у попередньому прикладі це те саме, що:
// Object.getPrototypeOf(this).getGreeting.call(this)
return super.getGreeting() + ", hi!";
}
};
Виклик super.getGreeting()
— це те саме, що Object.getPrototypeOf(this).getGreeting.call(this)
у цьому контексті. Так само ви можете викликати будь–який метод прототипу об’єкта з використанням посилання super
всередині лаконічного методу. Спроба використати super
поза лаконічними методами призведе до синтаксичної помилки, як у цьому прикладі:
let friend = {
getGreeting: function() {
return super.getGreeting() + ", hi!";
}
};
friend.getGreeting(); // кине помилку!
Цей приклад використовує іменовану властивість з функцією, тому виклик friend.getGreeting()
кидає помилку, оскільки super
не є валідним у цьому контексті.
Посилання super
справді є дуже потужним, коли ви маєте кілька рівнів наслідування, оскільки у цьому випадку, Object.getPrototypeOf()
більше не працює для всіх випадків. Наприклад:
let person = {
getGreeting() {
return "Hello";
}
};
// person — це прототип
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
// friend — це прототип
let relative = Object.create(friend);
console.log(person.getGreeting()); // "Hello"
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(relative.getGreeting()); // error!
Звернення до Object.getPrototypeOf()
спричинить помилку, коли relative.getGreeting()
буде викликаний. Це тому, що this
є relative
, а прототипом relative
є об’єкт friend
. Коли friend.getGreeting().call()
викликається з relative
у якості this
, процес починається знову і знову та продовжується рекурсивно доки не виникне переповнення стеку.
Цю проблему важко вирішити у ECMAScript 5, проте з ECMAScript 6 та super
це просто:
let person = {
getGreeting() {
return "Hello";
}
};
// person — це прототип
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
// friend — це прототип
let relative = Object.create(friend);
console.log(person.getGreeting()); // "Hello"
console.log(friend.getGreeting()); // "Hello, hi!"
console.log(relative.getGreeting()); // "Hello, hi!"
Оскільки посилання super
не є динамічним, воно завжди посилає на правильний об’єкт. У цьому випадку, super.getGreeting()
завжди повертатиме person.getGreeting()
, незалежно від того скільки інших об’єктів наслідують цей метод.
Формальне визначення методів
До ECMAScript 6 поняття «метод» не було формально визначеним. Методами були просто властивості об’єктів, що містили функції замість даних. ECMAScript 6 формально визначає метод як функцію, що має внутрішню властивість [[HomeObject]]
, яка містить об’єкт, якому цей метод належить. Розгляньте наступне:
let person = {
// метод
getGreeting() {
return "Hello";
}
};
// не метод
function shareGreeting() {
return "Hi!";
}
Цей приклад задає person
з єдиним методом getGreeting()
. [[HomeObject]]
для getGreeting()
буде person
внаслідок присвоєння цієї функції безпосереньо до об’єкта. З іншого боку, функція shareGreeting()
, не має заданого [[HomeObject]]
тому, що вона не була присвоєна об’єкту під час створення. У більшості випадків, ця відмінність не є важливою, проте вона стає дуже важливою при використанні посилання super
.
Будь—яке посилання до super
використовує [[HomeObject]]
для визначення того, що робити. Першим кроком є виклик Object.getPrototypeOf()
для [[HomeObject]]
, щоб отримати посилання на прототип. Потім у прототипі шукається функція з таким самим ім’ям. Нарешті встановлюється this
-зв’язування і метод викликається. Якщо функція не має [[HomeObject]]
, або має відмнінний [[HomeObject]]
від очікуваного, тоді цей процес припиниться і кинуться помилки, як у цьому шматочку коду:
let person = {
getGreeting() {
return "Hello";
}
};
// person — це прототип
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
function getGlobalGreeting() {
return super.getGreeting() + ", yo!";
}
console.log(friend.getGreeting()); // "Hello, hi!"
getGlobalGreeting(); // кидається помилка
Виклик friend.getGreeting()
повертає рядок, тоді як виклик getGlobalGreeting()
кидає помилку про неправильне використання ключового слова super
. Оскільки функція getGlobalGreeting()
не має [[HomeObject]]
, неможливо визначити метод у наслідуваного прототипу.
Цікаво, що ситуація не зміниться, якщо getGlobalGreeting()
присвоїти як метод об’єкту friend
, ось так:
// prototype is person
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
}
};
Object.setPrototypeOf(friend, person);
function getGlobalGreeting() {
return super.getGreeting() + ", yo!";
}
console.log(friend.getGreeting()); // "Hello, hi!"
// присвоюємо getGreeting глобальну функцію
friend.getGreeting = getGlobalGreeting;
friend.getGreeting(); // кидає помилку
Тут функція getGlobalGreeting()
перезаписує попередньо визначений метод getGreeting()
об’єкту friend
. Звернення до friend.getGreeting()
, у цьому випадку, також призводить до помилки, тому що тепер getGlobalGreeting()
буде методом, який не має [[HomeObject]]
. Значення [[HomeObject]]
встановлюється лише тоді, коли функція створюється, тож навіть присвоєння методу до об’єкту не вирішує проблеми.
Підсумок
Об’єкти є стовпом програмування на JavaScript, а ECMAScript 6 зробив кілька корисних змін об’єктів, що роблять їх потужнішими та простішими.
ECMAScript 6 вносить кілька змін до об’єктних літералів. Скорочене визначення властивостей спрощує присвоєння властивостям значень змінних з поточної області видимості. Обчислювані імена властивостей дозволяють вам встановлювати нелітеральні значення у якості імен властивостей так, як це було можливо в інших частинах мови. Лаконічні методи дозволяють вам писати значно менше символів для визначення методів у об’єктних літералах, шляхом упускання двокрапки та ключового слова function
. ECMAScript 6 послаблює перевірку дублювання імен властивостей у об’єктних літералах, тому ви можете мати дві властивості з однаковими іменами в одному об’єкті без виникнення помилки.
Метод Object.assign()
спрощує зміну кількох властивостей одного об’єкту одночасно. Він може виявитись дуже корисним, якщо ви користуєтесь mixin–патерном. Метод Object.is()
обчислює строгу рівність будь–яких значення, будучи ефектнивною та безпечнішою версією ===
при роботі зі спеціальними значеннями JavaScript.
У ECMAScript 6 тепер чітко визначений порядок перелічення властивостей. При переліченні властивостей, нумеровані ключі йдуть попереду у порядку зростання, після йдуть рядкові ключі у порядку встановлення та символьні ключі у порядку встановлення.
Завдяки методу Object.setPrototypeOf()
тепер можливо змінювати прототип об’єкту після того, як об’єкт було створено.
Нарешті, ви можете використовувати ключове слово super
для виклику методів прототипу об’єкту. Він може використовуватись як самостійно, так і з методом, як от super()
, або з посиланням до самого прототипа, як от super.getGreeting()
. В обох випадках, this
-зв’язування буде встановлено автоматично для роботи з поточним значенням this
.