Объект JavaScript и его свойства (поля)
Объект в JS — это набор свойств и их значений в памяти, на которые можно сослаться с помощью идентификатора.
Все объекты в JavaScript являются потомками Object; все объекты наследуют методы и свойства из прототипа объекта Object.prototype, хотя они и могут быть переопределены.
1 2 3 4 5 6 7 8 9 |
// объект user const user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow" }, }; |
Свойство объекта – это пара «ключ: значение»:
- ключ — это идентификатор (или имя) свойства (тип String или Symbol),
- значения могут быть любыми (любой тип, включая другие объекты).
Свойства объекта также иногда называют полями объекта.
Т.к. значения свойств могут иметь любой тип (включая другие объекты), это позволяет строить сложные, разветвлённые иерархии данных.
Свойства, значениями которых являются функции, называются методами.
После имени свойства следует двоеточие ":", и затем указывается значение свойства. Если в объекте несколько свойств, то они перечисляются через запятую.
Имя свойства должно соответствовать ограничениям, касающимся имён переменных JavaScript (например, должно содержать только буквы, цифры или символы $ и _), но может быть невалидным или состоять из нескольких слов, но тогда оно должно быть заключено в кавычки:
1 2 3 4 5 6 |
const user = { "user-adress": { // в кавычках, т.к. имя невалидное (через дефис) country: "Russia", sity: "Moscow", // "висячая" запятая }, // "висячая" запятая }; |
Последнее свойство объекта может заканчиваться запятой («висячая запятая»). Такой подход упрощает добавление, удаление и перемещение свойств, так как все строки объекта становятся одинаковыми.
Есть два типа свойств, отличающихся определенными атрибутами:
- свойство-значение (ассоциирует ключ со значением);
- свойство-акцессор (ассоциирует ключ с одной из двух функций-акцессоров: геттер и сеттер).
Особенности сравнения двух объектов
Два объекта не считаются равными, даже если они будут иметь одинаковые наборы свойств с одинаковыми значениями.
Два массива не считаются равными, даже если они имеют один и тот же набор элементов, следующих в том же порядке.
Чтобы подчеркнуть отличие от простых типов JavaScript, объекты иногда называют ссылочными типами. Если следовать этой терминологии, значениями объектов являются ссылки, и можно сказать, что объекты сравниваются по ссылке (см. ниже):
- ВАЖНО! значения двух объектов считаются равными тогда и только тогда, когда они ссылаются на один и тот же объект в памяти.
1 2 3 4 5 |
let myName = { name: "Alex" }; let hisName = myName; // копирование по ссылке hisName.name='Olga' console.log(myName); // {name: "Olga"} console.log(hisName); // {name: "Olga"} |
Как следует из примера выше, операция присваивания объекта (или массива) переменной фактически присваивает ссылку: она не создает новую копию объекта. Если в программе потребуется создать новую копию объекта или массива, необходимо будет явно скопировать свойства объекта или элементы массива.
Читайте также JavaScript: Ядро
Виды объектов в JavaScript
Все объекты можно условно разделить на несколько видов:
- глобальный объект;
- объектные типы:
- собственно объекты (коллекции свойств и их значений);
- массивы (коллекции данных, упорядоченных в виде списка элементов);
- функции (фрагменты программного кода (подпрограммы), позволяющие формализовать определённую логику поведения и обработки данных).
- объекты-обертки.
Глобальный объект - это объект JavaScript, свойства которого являются глобальными идентификаторами, доступными из любого места программы на JavaScript.
Когда выполняется запуск интерпретатора JavaScript (или когда вебброузер загружает новую страницу), создается новый глобальный объект, в котором инициализируется начальный набор свойств, определяющих:
- глобальные свойства, такие как undefined, Infinity и NaN;
- глобальные функции, такие как isNaN(), parseInt() и eval();
- функции-конструкторы, такие как Date(), RegExp(), String(), Object() и Array();
- глобальные объекты, такие как Math и JS0N.
Имена первоначально устанавливаемых свойств глобального объекта можно отыскать по именам в справочном разделе по базовому JavaScript или в описании самого глобального объекта, под именем «Global».
В программном коде верхнего уровня, т. е. в JavaScript-коде, который не является частью функции, сослаться на глобальный объект можно посредством ключевого слова this :
1 |
var global = this; // определение глобальной переменной для ссылки на глобальный объект |
В клиентском JavaScript имеется объект Window, определяющий другие глобальные свойства, описание которых можно найти в справочном разделе по клиентскому JavaScript. Этот глобальный объект имеет свойство window , ссылающееся на сам объект, которое можно использовать вместо ключевого слова this для ссылки на глобальный объект Window.
Объект Window определяет базовые глобальные свойства, а также дополнительные глобальные свойства, характерные для веб-броузеров и клиентского JavaScript. Однако этот специальный объект может также хранить глобальные переменные программы. Если программа объявляет глобальную переменную, она становится свойством глобального объекта.
Создание объекта в JavaScript
В общем случае, в JavaScript новые объекты могут создаваться следующими способами:
- пустой объект:
- с помощью фигурных скобок {…} с необязательным списком свойств (литеральная нотация);
- методом Object.create(null);
- с помощью функции-"конструктора" объекта (через new Object(nameKlass), где nameKlass - функция-конструктор);
- клонировать существующий объект и расширить его:
- делегированием через прототипы (объект ссылается или делегирует другому объекту):
- с помощью метода Object.create(nameKlass) (создание объекта из прототипа nameKlass);
- с использованием constructor;
- конкатенацией объектов (объект формируется путем добавления новых свойств к объекту из других существующих объектов).
- делегированием через прототипы (объект ссылается или делегирует другому объекту):
1 2 3 4 5 6 7 8 9 10 |
let nameObject = {}; // синтаксис "литерал объекта" или "литеральная нотация" let nameObject = new Object() // синтаксис "конструктор объекта", например: function Car(make, model, year) { // функция-конструктор this.make = make; this.model = model; this.year = year; } var mycar = new Car("Eagle", "Talon TSi", 1993); // Car {make: "Eagle", model: "Talon TSi", year: 1993} |
Особенности создания пустого объекта:
1 2 3 4 5 6 7 8 9 10 11 12 |
myObj = { name: "Alex", surname: undefined, "user-adress": { country: "Russia", sity: "Moscow", }, } let oldObj = {}; // __proto__: Object let newObj = new Object(); // __proto__: Object let objCreate = Object.create(null); // No properties |
Создание объектов различными способами с передачей объекта в качестве аргумента:
1 2 3 4 5 6 7 8 |
let oldObj = { myObj: {} }; // {myObj: {…}} (создается свойство myObj со значением в виде объекта myObj) let newObj = new Object(oldObj); // копирование по ссылке! console.log(newObj === oldObj); // true (!!!) (oldObj скопирован по ссылке) // при изменении свойств newObj будут изменены свойства объекта oldObj let objCreate = Object.create(oldObj); // __proto__: Object (oldObj) (создается объект objCreate с прототипом oldObj) console.log(objCreate === oldObj); // false (oldObj - прототипное наследование) |
Метод Object.create() создаёт новый объект с указанными объектом прототипа и свойствами.
1 |
Object.create(proto[, propertiesObject]) |
Параметры:
- proto - объект, который станет прототипом вновь созданного объекта;
- propertiesObject - (необязательный параметр) объект, чьи собственные перечисляемые свойства (то есть такие, которые определены на самом объекте, а не унаследованы по цепочке прототипов) указывают дескрипторы свойств, добавляемых в новый объект.
Метод Object.create() возвращает новый объект (не копию по ссылке) с заданным прототипом и свойствами.
1 2 3 4 5 6 7 8 9 10 11 12 |
const user = { firstName: "Alex", age: "40", }; const newUser = Object.create(user); const oldUser = Object.create(user, { car: { value: "Nissan" } }); let value = Object.getPrototypeOf(oldUser); console.log(newUser.firstName); // Alex console.log(oldUser.car); // Nissan console.log(value); // {firstName: "Alex", age: "40"} |
Обычно используют вариант с фигурными скобками {...}. Такое объявление называют литералом объекта (литеральной нотацией).
Литеральная инициализация объекта сразу задаёт определённое количество начальных свойств (полей), но в процессе работы приложения свойства (поля) объекта могут добавляться и (или) удаляться.
Таким образом, при использовании литерального синтаксиса {...} мы сразу можем:
- сразу поместить в объект несколько свойств в виде пар «ключ: значение»;
- создать пустой объект и добавить в него свойства позже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Добавление свойств в объект при его создании const user = { firstName: "Alex", lastName: "NAV", Age: "40", isAdmin: true, email: "email@email.ru", "user-adress": { // в кавычках, т.к. имя невалидное (через дефис) country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"] }; |
1 2 3 4 5 6 7 8 |
// Создание пустого объекта и добавление в него свойств позже let obj = {}; obj.name = "messi"; obj.year = 2018; obj.speak = function() { return "My Name is " + this.name + "and this is year " + this.year; }; |
Объект, объявленный через const, может быть изменён. Объявление const защищает от изменений только само именование объекта (в примере выше - nameObject), но не его свойства.
1 2 3 4 5 6 7 8 9 10 11 12 |
const user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow" }, }; // Изменение значений свойств в объекте, объявленном через const, допускается user.firstName = "Olga"; // {firstName: "Olga", lastName: "NAV", Age: "40", …} user["user-adress"].country = "USA"; |
В реальном коде часто необходимо использовать существующие переменные как значения для свойств с тем же именем.
Например:
1 2 3 4 5 6 7 8 9 |
function makeUser(name, age) { return { name: name, age: age }; } let user = makeUser("John", 30); alert(user.name); // John |
В примере выше название свойств name и age совпадают с названиями переменных, которые мы подставляем в качестве значений этих свойств. Вместо name:name мы можем написать просто name:
1 2 3 4 5 6 |
function makeUser(name, age) { return { name, // то же самое, что и name: name age // то же самое, что и age: age }; } |
Замечание: Зарезервированные слова разрешено использовать как имена свойств (в отличие от имён переменных, которые не могут совпадать с зарезервированными словами, такими как «for», «let», «return» и т.д.)
1 2 3 4 5 6 7 |
let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6 |
Работа со свойствами объекта в JavaScript
Обращение к свойствам объекта в JS:
Для обращения к свойствам объекта в JavaScript используется два варианта записи:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const user = { firstName: "Alex", lastName: "NAV", Age: "40", isAdmin: true, email: "email@email.ru", "user-adress": { // в кавычках, т.к. имя невалидное (через дефис) country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"] }; let value; // Вариант 1 (через точку) value = user.lastName; console.log(value); // NAV |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const user = { firstName: "Alex", lastName: "NAV", Age: "40", isAdmin: true, email: "email@email.ru", "user-adress": { // в кавычках, т.к. имя невалидное (через дефис) country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"] }; let value; // Вариант 2 (запись через квадратные скобки) value = user["user-adress"]; // {country: "Russia", sity: "Moscow"} value = user["user-adress"].country; // Russia value = user["user-adress"]["country"]; // Russia let prop = "skills"; value = user[prop]; // (3) ["html", "css", "JS"] (получение свойства из переменной) value = user[prop][0]; // html (получение элемента из свойства) |
Кроме того, квадратные скобки ( [] ) в коде объектов также используются:
- для обращения к свойству, имя которого является результатом выражения;
- для ключей, заданных в кавычках (невалидных);
- для обращения к вычисляемому свойству.
Помните! В JavaScript к свойствам, начинающимся с цифры, невозможно обратиться посредством точечной нотации; к ним можно обратиться только с помощью скобочной нотации.
1 2 3 4 5 6 |
// Обращение к свойству, имя которого является результатом выражения let user = { name: "Alex", age: 40, }; |
1 2 3 4 |
// Для задания ключей с невалидными именами let key = "likes birds"; user[key] = true; // то же самое, что и user["likes birds"] = true; |
Квадратные скобки в литеральной нотации могут использоваться для создания вычисляемого свойства.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let fruit = prompt("Какой фрукт купить?", "apple"); let bag = { [fruit]: 5, // имя свойства будет взято из переменной fruit }; alert( bag.apple ); // 5, если fruit = "apple" (undefined, если fruit != "apple") // или более сложное выражение в [] let firstName = "Alex"; let age = { [firstName + " NAV"]: 40 }; console.log(age); // Object { "Alex NAV": 40 } |
Смысл вычисляемого свойства прост: запись [fruit] означает, что имя свойства необходимо взять из переменной fruit.
И если посетитель введёт слово "apple", то в объекте bag теперь будет лежать свойство {apple: 5}.
Мы можем использовать и более сложные выражения в квадратных скобках:
В большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки.
1 2 3 4 5 6 7 8 9 10 11 |
// Запись (добавление) нового свойства в объект user.isAdmin = true; // добавлено новое свойство 'isAdmin' типа Boolean user.gender = "Male"; // добавлено новое свойство 'gender' типа String console.log(user.gender); // Male // Добавление свойства в несуществующий вложенный объект приведет к ошибке user.prop.basic = "basic"; // Uncaught TypeError: Cannot set property 'basic' of undefined // (для исправления ошибки надо сначала создать вложенный объект prop) |
Удаление свойства объекта JS оператором delete:
1 |
delete <em>expression</em> |
где результат вычисления выражения должен быть ссылкой на свойство (объекта), например:
1 2 3 4 |
delete object.property delete object['property'] delete object[index] delete property // удаляет свойства глобального объекта, или, используя инструкцию with, свойства объекта, на который ссылается инструкция |
1 2 3 |
delete user.age; delete user[age]; delete user["user-adress"]; |
Если результат вычисления выражения не является свойством (объекта), delete ничего не делает. Ещё пример:
1 2 3 4 5 6 7 |
var x = 1; var obj = { x: 10 }; delete x; // так как x не свойство объекта, то delete ничего не делает delete obj.x; console.log(x, obj.x); // 1 undefined |
Особенность объектов в том, что можно получить доступ к любому свойству. Даже если свойства не существует – ошибки не будет!
При обращении к свойству, которого нет, возвращается undefined.
Способы проверки существования свойства объекта JavaScript:
- сравнением его с undefined;
- с помощью оператора "in".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Сравнением с undefined let user = {}; alert( user.noSuchProperty === undefined ); // true означает "свойства нет" // С помощью оператора "in" let user = { name: "Alex", age: 40 }; alert( "age" in user ); // true, user.age существует alert( "blabla" in user ); // false, user.blabla не существует // или let user = { age: 30 }; let key = "age"; alert( key in user ); // true, имя свойства было взято из переменной key |
Функция проверки объекта на пустоту
1 2 3 4 5 6 7 8 |
// Функция проверки наличия свойств объекта (проверка объекта на пустоту) function isEmpty(obj) { for (let key in obj) { // если цикл начнет выполняться, значит объект не пустой (имеет свойства) return false; } return true; // цикл НЕ выполнялся, значит объект пустой (не имеет свойств) } console.log(isEmpty(phone)); |
Замечание по строгому сравнению "=== undefined"
Если свойство существует, но содержит значение undefined, то строгого сравнения "=== undefined" не достаточно для проверки наличия свойства:
1 2 3 4 5 6 7 8 9 |
let obj = { test: undefined // свойство test со значением undefinid }; // Вариант проверки неверный alert( obj.test ); // выведет undefined, но свойство СУЩЕСТВУЕТ! // Вариант проверки необходимый alert( "test" in obj ); // true, свойство существует! |
Подобные ситуации случаются очень редко, так как undefined обычно явно не присваивается. Для «неизвестных» или «пустых» свойств мы используем значение null. Таким образом, оператор in является экзотическим гостем в коде.
Для перебора всех свойств объекта используется цикл for..in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const user = { firstName: "Alex", lastName: "NAV", Age: "40", isAdmin: true, email: "email@email.ru", "user-adress": { // в кавычках, т.к. имя невалидное (через дефис) country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"] }; // Цикл перебора свойств объекта for (let key in user) { console.log(key + " " + user[key]); //выведет имя свойства, пробел, значение свойства } |
Метод Object.keys() возвращает массив из собственных перечисляемых свойств переданного объекта в том же порядке, в котором они бы обходились циклом for...in.
Отличие for...in от Object.keys():
- цикл for...in перечисляет свойства и из цепочки прототипов.
1 |
Object.keys(obj) |
где obj - объект, чьи собственные перечисляемые свойства будут возвращены.
Метод Object.keys возвращает массив строковых элементов, соответствующих именам перечисляемых свойств, найденных непосредственно в самом объекте. Порядок свойств такой же, как и при ручном перечислении свойств в объекте через цикл.
Например (отсюда):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var arr = ['a', 'b', 'c']; console.log(Object.keys(arr)); // Array(3) [ "0", "1", "2" ] // Массивоподобный объект var obj = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.keys(obj)); // Array(3) [ "0", "1", "2" ] // Массивоподобный объект со случайным порядком ключей var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; console.log(Object.keys(an_obj)); // Array(3) [ "2", "7", "100" // Свойство getFoo является не перечисляемым свойством var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } }); my_obj.foo = 1; console.log(Object.keys(my_obj)); // консоль: ['foo'] |
Подробнее о перечисляемых и не перечисляемых свойствах.
В ES5, если аргумент метода не является объектом (является примитивным значением), будет выброшено исключение TypeError. В ES2015 такой аргумент будет приведён к объекту.
Метод Object.entries() возвращает массив собственных перечисляемых свойств указанного объекта в формате [key, value], в том же порядке, что и в цикле for...in (разница в том, что for-in также перечисляет свойства из цепочки прототипов).
Порядок элементов в массиве который возвращается Object.entries() не зависит от того как объект объявлен. Если существует необходимость вывести в определенном порядке, то массив должен быть отсортирован до вызова метода, например Object.entries(obj).sort((a, b) => a[0] - b[0]);.
1 |
Object.entries(obj) |
где obj - объект, чьи перечислимые свойства будут возвращены в виде массива [key, value].
Метод возвращает массив перечислений собственных свойств объекта с парами [key, value]. Порядок свойств тот же, что и при прохождении циклом по свойствам объекта вручную.
Примеры:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
var obj = { foo: "bar", baz: 42 }; console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ] // Массивоподобный объект var obj = { 0: "a", 1: "b", 2: "c" }; console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ] // Массивоподобный объект со случайным порядком ключей var an_obj = { 100: 'a', 2: 'b', 7: 'c' }; console.log(Object.entries(an_obj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ] // Объект с не перечисляемым свойством getFoo var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } }); my_obj.foo = "bar"; console.log(Object.entries(my_obj)); // [ ['foo', 'bar'] ] // Аргумент, не являющийся объектом, приводится к объекту console.log(Object.entries("foo")); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ] // Для примитивных типов возвращается пустой массив (т.к. у них нет свойств) console.log(Object.entries(100)); // [ ] // Перебор свойств объекта const obj = { a: 5, b: 7, c: 9 }; for (const [key, value] of Object.entries(obj)) { // получение значений key и value через деструктуризацию console.log(`${key} ${value}`); // "a 5", "b 7", "c 9" } // или так const obj = { a: 5, b: 7, c: 9 }; Object.entries(obj).forEach(([key, value]) => { console.log(`${key} ${value}`); // "a 5", "b 7", "c 9" }); // Преобразование Object в Map var obj = { foo: "bar", baz: 42 }; var map = new Map(Object.entries(obj)); console.log(map); // Map { foo → "bar", baz → 42 } |
Статический метод Reflect.ownKeys() возвращает массив имён, а также Symbol собственных полей объекта.
Синтаксис:
Reflect.ownKeys(target)
где target - объект, из которого получаем собственные ключи.
Возвращаемое значение:
- массив собственных полей объекта target.
Reflect.ownKeys(target) выбрасывает исключение TypeError, если target не является Object.
Метод Reflect.ownKeys() является эквивалентом метода Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)).
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let user = { firstName: "Alex", isAdmin: true, "user-adress": { country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"], getFoo: { value: function () { return this.foo; }, }, [Symbol()]: "Symbol()", }; console.log(Object.getOwnPropertyNames(user)); // (5) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo'] console.log(Object.keys(user)); // (5) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo'] // Reflect.ownKeys() выводит также и ключи Symbol console.log(Reflect.ownKeys(user)); // (6) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo', Symbol()] |
Метод Object.fromEntries() преобразует список пар ключ-значение в объект.
1 |
Object.fromEntries(iterable); |
где iterable - итерируемый объект (такой как Array или Map или другие объекты, реализующие протокол итерации).
Метод Object.fromEntries() принимает список пар ключ-значение и возвращает новый объект, свойства которого задаются этими записями. Ожидается, что аргумент iterable будет объектом, который реализует метод @@iterator. Итератор создает двуэлементный массивоподобный объект, первый элемент которого является значением, используемым в качестве ключа свойства, а второй элемент — значением свойства, связанного с этим ключом.
Object.fromEntries() выполняет процедуру, обратную Object.entries().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Преобразование Map в Object const map = new Map([ ['foo', 'bar'], ['baz', 42] ]); const obj = Object.fromEntries(map); console.log(obj); // { foo: "bar", baz: 42 } // Преобразование Array в Object const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]; const obj = Object.fromEntries(arr); console.log(obj); // { 0: "a", 1: "b", 2: "c" } // Трансформации (изменение) объектов const object1 = { a: 1, b: 2, c: 3 }; const object2 = Object.fromEntries( Object.entries(object1).map(([key, val]) => [key, val * 2]) ); console.log(object2); // { a: 2, b: 4, c: 6 } |
Свойства объекта упорядочены особым образом:
- свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания.
Термин «целочисленное свойство» означает строку, которая может быть преобразована в целое число и обратно без изменений (например, свойства 5, 7, 24, 69 и т.п. - целочисленные, а свойства "+49" или "1.2" целочисленными не являются).
Пример:
1 2 3 4 5 |
// Math.trunc - встроенная функция, которая удаляет десятичную часть alert( String(Math.trunc(Number("49"))) ); // "49", свойство целочисленное alert( String(Math.trunc(Number("+49"))) ); // "49", не то же самое, свойство не целочисленное alert( String(Math.trunc(Number("1.2"))) ); // "1", не то же самое, свойство не целочисленное |
Пример сортировки целочисленных свойств:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Сортировка целочисленных свойств let codes = { "49": "Германия", "41": "Швейцария", "44": "Великобритания", "1": "США" }; for (let code in codes) { alert(code); // выведет по порядку: 1, 41, 44, 49 } |
Для изменения сортировки необходимо сделать свойства не целочисленными (например, добавив "+"):
1 2 3 4 5 6 7 8 9 10 |
let codes = { "+49": "Германия", "+41": "Швейцария", "+44": "Великобритания", "+1": "США" }; for (let code in codes) { alert(code); // выведет по порядку: +49, +41, +44, +1 } |
Копирование объектов в JS (по ссылке)
Примитивные типы: строки, числа, логические значения – присваиваются и копируются «по значению», например:
1 2 |
let message = "Hello!"; let phrase = message; |
В результате мы имеем две независимые переменные, каждая из которых хранит строку "Hello!".
Объекты ведут себя иначе. Одним из фундаментальных отличий объектов от примитивных типов данных является то, что они хранятся и копируются «по ссылке».
Переменная хранит не сам объект, а его «адрес в памяти», другими словами «ссылку» на него. Когда переменная объекта копируется – копируется ссылка, сам же объект не дублируется.
1 2 3 |
let user = { name: "John" }; let admin = user; // копируется ссылка, сам объект не дублируется |
Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект, что позволяет использовать любую из переменных для доступа к объекту и изменения его свойств.
1 2 3 4 5 6 |
let user = { name: 'Alex' }; let admin = user; admin.name = 'Olga'; // изменено по ссылке из переменной "admin" alert(user.name); // Olga, изменения видны по ссылке из переменной "user" |
Сравнение объектов в JS
Операторы равенства == и строгого равенства === для объектов работают одинаково.
Два объекта равны только в том случае, если это один и тот же объект.
Например, если было выполнено копирование (по ссылке) , т.е. две переменные ссылаются на один и тот же объект, то они равны:
1 2 3 4 5 |
let a = {}; let b = a; // копирование по ссылке alert( a == b ); // true, обе переменные ссылаются на один и тот же объект alert( a === b ); // true |
Не будут равны объекты хотя и пустые, но независимые:
1 2 3 4 5 |
// два пустых, но независимых объекта let a = {}; let b = {}; alert( a == b ); // false |
Объекты преобразуются в примитивы при сравнении:
- типа obj1 > obj2 или
- с примитивом obj == 5 .
Дублирование и объединение объектов. Метод Object.assign и оператор spread
Поверхностное клонирование (дублирование) объектов JS оператором for
Поверхностная копия скопирует только свойства верхнего уровня, вложенные объекты будут скопированы "по ссылке" и могут использоваться как оригиналом, так и копией.
Для поверхностного дублирования нужно создать новый объект и повторить структуру дублируемого объекта, перебирая его свойства и копируя их.
1 2 3 4 5 6 7 8 9 10 11 |
let user = { name: "Alex", age: 40 }; // Создаем новый пустой объект let clone = {}; // копируем в него все свойства объекта user for (let key in user) { clone[key] = user[key]; } console.log(user==clone); // false, т.к. объекты разные |
Поверхностное клонирование (дублирование) объектов JS методом Object.assign
Используется для дублирования и объединения объектов в JS.
1 |
Object.assign(dest, [src1, src2, src3...]) |
Аргументы dest, и src1, ..., srcN являются объектами. Метод копирует свойства всех объектов src1, ..., srcN в объект dest. То есть, свойства всех перечисленных объектов, начиная со второго, последовательно копируются в первый объект.
Если принимающий объект (dest) уже имеет свойство с таким именем, оно будет перезаписано.
После копирования метод возвращает объект dest.
1 2 3 4 5 6 7 8 9 |
let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; // копируем все свойства из permissions1 и permissions2 в user Object.assign(user, permissions1, permissions2); // теперь user = { name: "John", canView: true, canEdit: true } |
Использование Object.assign для простого клонирования (дублирования):
1 2 3 4 5 |
let user = { name: "John", age: 30 }; let clone = Object.assign({}, user); console.log(clone); // Object { name: "John", age: 30 } |
Все свойства объекта user будут скопированы в пустой объект, и ссылка на этот объект будет в переменной clone.
Чтобы избежать появления значения undefined при доступе к несуществующему свойству объекта необходимо:
- определить объект defaults, который содержит значения свойств по умолчанию;
- вызвать функцию Object.assign({ }, defaults), чтобы создать новый объект.
Например, новый объект options получает все свойства из unsafeOptions, недостающие берутся из defaults:
1 2 3 4 5 6 7 8 9 10 |
const unsafeOptions = { fontSize: 18, }; const defaults = { fontSize: 16, color: "black", }; const options = Object.assign({}, defaults, unsafeOptions); options.fontSize; // => 18 options.color; // => 'black' |
unsafeOptions содержит только свойство fontSize. Объект defaults указывает значения по умолчанию для fontSize и color. Object.assign() принимает первый аргумент как целевой объект {}. Целевой объект получает значение свойства fontSize из объекта-источника unsafeOptions. А значение свойства color — из объекта-источника defaults, поскольку unsafeOptions не содержит color.
Важен порядок, в котором перечислены исходные объекты: первый пришёл – первый ушёл.
Теперь возможно получить доступ к любому свойству объекта options, включая options.color, который изначально был недоступен в unsafeOptions.
Заполнение неполного объекта значением по умолчанию является эффективной стратегией, обеспечивающей безопасность и стабильность вашего кода. Независимо от ситуации, объект всегда содержит полный набор свойств, и появление undefined невозможно.
Поверхностное клонирование (дублирование) объектов JS оператором spread (spread syntax)
Более простой способ поверхностного копирования объекта и установки значений по умолчанию для свойств объекта - с использованием оператора spread ( ... ), который позволяет расширять свойства при инициализации объектов.
Вместо вызова Object.assign() используйте spread syntax - синтаксис распространения объекта ( ... ), чтобы скопировать в целевой объект все собственные и перечисляемые свойства из исходных объектов:
1 2 3 4 5 6 7 8 9 10 |
const unsafeOptions = { fontSize: 18, }; const defaults = { fontSize: 16, color: "black", }; const options = { ...defaults, ...unsafeOptions }; console.log(options.fontSize); // => 18 console.log(options.color); // => 'black' |
Инициализатор объекта распространяет свойства из исходных объектов defaults и unsafeOptions.
Важен порядок, в котором указаны исходные объекты: свойства более позднего исходного объекта перезаписывают более ранние.
Обратите внимание, что Object.assign() запускает setters, а spread syntax нет.
Обратите внимание, что вы не можете заменить или имитировать функцию Object.assign():
1 2 3 4 5 6 7 8 9 |
var obj1 = { foo: "bar", x: 42 }; var obj2 = { foo: "baz", y: 13 }; const merge = (...objects) => ({ ...objects }); var mergedObj = merge(obj1, obj2); // Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } } var mergedObj = merge({}, obj1, obj2); // Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } } |
В приведенном выше примере оператор распространения не работает так, как можно было бы ожидать: он распространяет массив аргументов в литерал объекта благодаря параметру rest.
Глубокое клонирование (дублирование) сложных объектов (содержащих вложенные объекты)
Глубокая копия продублирует каждый объект на пути клонирования. Оригинал и клонированный объект не будут иметь ничего общего (будут являться разными объектами).
Пусть есть объект user, содержащий вложенный объект "user-adress":
1 2 3 4 5 6 7 8 9 10 |
const user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow" }, }; alert( user["user-adress"].country); // Russia |
В процессе клонирования родительского объекта недостаточно просто скопировать вложенный объект "user-adress": он будет скопирован по ссылке, а значит объекты clone и user в своих свойствах "user-adress" будут ссылаться на один и тот же объект.
Чтобы исправить это положение, мы должны в цикле клонирования делать проверку не является ли значение user[key] объектом, и если это так – копировать и его структуру тоже. Это называется «глубокое клонирование».
Существует стандартный алгоритм глубокого клонирования, Structured cloning algorithm. Он решает описанную выше задачу, а также более сложные задачи. Мы можем использовать реализацию этого алгоритма из JavaScript-библиотеки lodash, метод _.cloneDeep(obj).
Глубокое клонирование объектов через JSON
Глубокое клонирование объектов
1 2 |
let deepCopy = JSON.parse(JSON.stringify(obj)); console.log(deepCopy); //{ name: 'messi', year: 2019 } |
1 2 3 4 5 6 7 |
const user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow" } }; let deepCopy = JSON.parse(JSON.stringify(user)); console.log(deepCopy); // Object { firstName: "Alex", lastName: "NAV", "user-adress": {…} } |
ВАЖНО! Этот метод нельзя использовать для копирования методов (т.е. функций) объекта, которые были написаны пользователем. Методы объекта копируются с помощью метода Object.assign.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const user = { firstName: "Alex", lastName: "NAV", "user-adress": { country: "Russia", sity: "Moscow" }, res: function() { return this.res; } }; let deepCopy = JSON.parse(JSON.stringify(user)); console.log(deepCopy); // Object { firstName: "Alex", lastName: "NAV", "user-adress": {…} } // функция res в копии отсутствует |