В этом разделе мы рассмотрим нововведения, которые касаются именно объектов.
По классам – чуть позже, в отдельном разделе, оно того заслуживает.
Короткое свойство
Зачастую у нас есть переменные, например, name
и isAdmin
, и мы хотим использовать их в объекте.
При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с аналогичным именем.
Например:
'use strict';
let name = "Вася";
let isAdmin = true;
let user = {
name,
isAdmin
};
alert( JSON.stringify(user) ); // {"name": "Вася", "isAdmin": true}
Вычисляемые свойства
В качестве имени свойства можно использовать выражение, например:
'use strict';
let propName = "firstName";
let user = {
[propName]: "Вася"
};
alert( user.firstName ); // Вася
Или даже так:
'use strict';
let a = "Мой ";
let b = "Зелёный ";
let c = "Крокодил";
let user = {
[(a + b + c).toLowerCase()]: "Гена"
};
alert( user["мой зелёный крокодил"] ); // Гена
Геттер-сеттер для прототипа
В ES5 для прототипа был метод-геттер:
Object.getPrototypeOf(obj)
В ES-2015 также добавился сеттер:
Object.setPrototypeOf(obj, newProto)
…А также «узаконено» свойство __proto__
, которое даёт прямой доступ к прототипу. Его, в качестве «нестандартного», но удобного способа работы с прототипом, реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт.
Object.assign
Функция Object.assign
получает список объектов и копирует в первый target
свойства из остальных.
Синтаксис:
Object.assign(target, src1, src2...)
При этом последующие свойства перезаписывают предыдущие.
Например:
'use strict';
let user = { name: "Вася" };
let visitor = { isAdmin: false, visits: true };
let admin = { isAdmin: true };
Object.assign(user, visitor, admin);
// user <- visitor <- admin
alert( JSON.stringify(user) ); // name: Вася, visits: true, isAdmin: true
Его также можно использовать для 1-уровневого клонирования объекта:
'use strict';
let user = { name: "Вася", isAdmin: false };
// clone = пустой объект + все свойства user
let clone = Object.assign({}, user);
Object.is(value1, value2)
Новая функция для проверки равенства значений.
Возвращает true
, если значения value1
и value2
равны, иначе false
.
Она похожа на обычное строгое равенство ===
, но есть отличия:
// Сравнение +0 и -0
alert( Object.is(+0, -0)); // false
alert( +0 === -0 ); // true
// Сравнение с NaN
alert( Object.is(NaN, NaN) ); // true
alert( NaN === NaN ); // false
Отличия эти в большинстве ситуаций некритичны, так что не похоже, чтобы эта функция вытеснила обычную проверку ===
. Что интересно – этот алгоритм сравнения, который называется SameValue, применяется во внутренних реализациях различных методов современного стандарта.
Методы объекта
Долгое время в JavaScript термин «метод объекта» был просто альтернативным названием для свойства-функции.
Теперь это уже не так. Добавлены именно «методы объекта», которые, по сути, являются свойствами-функциями, привязанными к объекту.
Их особенности:
- Более короткий синтаксис объявления.
- Наличие в методах специального внутреннего свойства
[[HomeObject]]
(«домашний объект»), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше.
Для объявления метода вместо записи "prop: function() {…}"
нужно написать просто "prop() { … }"
.
Например:
'use strict';
let name = "Вася";
let user = {
name,
// вместо "sayHi: function() {...}" пишем "sayHi() {...}"
sayHi() {
alert(this.name);
}
};
user.sayHi(); // Вася
Как видно, для создания метода нужно писать меньше букв. Что же касается вызова – он ничем не отличается от обычной функции. На данном этапе можно считать, что «метод» – это просто сокращённый синтаксис для свойства-функции. Дополнительные возможности, которые даёт такое объявление, мы рассмотрим позже.
Также методами станут объявления геттеров get prop()
и сеттеров set prop()
:
'use strict';
let name = "Вася", surname="Петров";
let user = {
name,
surname,
get fullName() {
return `${name} ${surname}`;
}
};
alert( user.fullName ); // Вася Петров
Можно задать и метод с вычисляемым названием:
'use strict';
let methodName = "getFirstName";
let user = {
// в квадратных скобках может быть любое выражение,
// которое должно вернуть название метода
[methodName]() { // вместо [methodName]: function() {
return "Вася";
}
};
alert( user.getFirstName() ); // Вася
Итак, мы рассмотрели синтаксические улучшения. Если коротко, то не надо писать слово «function». Теперь перейдём к другим отличиям.
super
В ES-2015 появилось новое ключевое слово super
. Оно предназначено только для использования в методах объекта.
Вызов super.parentProperty
позволяет из метода объекта получить свойство его прототипа.
Например, в коде ниже rabbit
наследует от animal
.
Вызов super.walk()
из метода объекта rabbit
обращается к animal.walk()
:
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
walk() {
alert(super.walk); // walk() { … }
super.walk(); // I'm walking
}
};
rabbit.walk();
Как правило, это используется в классах, которые мы рассмотрим в следующем разделе, но важно понимать, что «классы» здесь на самом деле ни при чём. Свойство super
работает через прототип, на уровне методов объекта.
При обращении через super
используется [[HomeObject]]
текущего метода, и от него берётся __proto__
. Поэтому super
работает только внутри методов.
В частности, если переписать этот код, оформив rabbit.walk
как обычное свойство-функцию, то будет ошибка:
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
walk: function() { // Надо: walk() {
super.walk(); // Будет ошибка!
}
};
rabbit.walk();
Ошибка возникнет, так как rabbit.walk
теперь обычная функция и не имеет [[HomeObject]]
. Поэтому в ней не работает super
.
Исключением из этого правила являются функции-стрелки. В них используется super
внешней функции. Например, здесь функция-стрелка в setTimeout
берёт внешний super
:
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
walk() {
setTimeout(() => super.walk()); // I'm walking
}
};
rabbit.walk();
Ранее мы говорили о том, что у функций-стрелок нет своего this
, arguments
: они используют те, которые во внешней функции. Теперь к этому списку добавился ещё и super
.
[[HomeObject]]
– не изменяемоеПри создании метода – он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить отдельно, и super
продолжит работать:
'use strict';
let animal = {
walk() { alert("I'm walking"); }
};
let rabbit = {
__proto__: animal,
walk() {
super.walk();
}
};
let walk = rabbit.walk; // скопируем метод в переменную
walk(); // вызовет animal.walk()
// I'm walking
В примере выше метод walk()
запускается отдельно от объекта, но всё равно, благодаря [[HomeObject]]
, сохраняется доступ к его прототипу через super
.
Это – скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для this
в методах – те же, что и для обычных функций. В примере выше при вызове walk()
без объекта this
будет undefined
.
Итого
Улучшения в описании свойств:
- Запись
name: name
можно заменить на простоname
- Если имя свойства находится в переменной или задано выражением
expr
, то его можно указать в квадратных скобках[expr]
. - Свойства-функции можно оформить как методы:
"prop: function() {}"
→"prop() {}"
.
В методах работает обращение к свойствам прототипа через super.parentProperty
.
Для работы с прототипом:
Object.setPrototypeOf(obj, proto)
– метод для установки прототипа.obj.__proto__
– ссылка на прототип.
Дополнительно:
- Метод
Object.assign(target, src1, src2...)
– копирует свойства из всех аргументов в первый объект. - Метод
Object.is(value1, value2)
проверяет два значения на равенство. В отличие от===
считает+0
и-0
разными числами. А также считает, чтоNaN
равно самому себе.