- Объекты и прототипы JavaScript
Объекты и прототипы
В этом разделе мы рассмотрим нововведения, которые касаются именно объектов.
По классам – чуть позже, в отдельном разделе, оно того заслуживает.
Короткое свойство
Зачастую у нас есть переменные, например, 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';
letuser
= {name
: "Вася" };
letvisitor
= {isAdmin
: false,visits
: true };
letadmin
= {isAdmin
: true };
Object
.assign(user
,visitor
,admin
);
// user <- visitor <- admin
alert(JSON
.stringify(user
) ); // name: Вася, visits: true, isAdmin: true
Его также можно использовать для 1-уровневого клонирования объекта:
'use strict';
letuser
= {name
: "Вася",isAdmin
: false };
// clone = пустой объект + все свойства user
letclone
=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 термин «метод объекта» был просто альтернативным названием для свойства-функции.
Теперь это уже не так. Добавлены именно «методы объекта», которые, по сути, являются свойствами-функциями, привязанными к объекту.
Их особенности:
1. Более короткий синтаксис объявления.
2. Наличие в методах специального внутреннего свойства [[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';
letname
= "Вася",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();
}
};
letwalk
=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
равно самому себе.