Автор НиколайНа чтение 30 мин.Просмотров55Опубликовано
Сейчас мы знаем о следующих сложных структурах данных:
Объекты для хранения именованных коллекций.
Массивы для хранения упорядоченных коллекций.
Но этого не всегда достаточно для решения повседневных задач. Поэтому также существуют Map и Set .
Как мы знаем из главы Типы данных , в JavaScript существует 8 типов данных. Семь из них называются «примитивными», так как содержат только одно значение (будь то строка, число или что-то другое).
Объекты же используются для хранения коллекций различных значений и более сложных сущностей. В JavaScript объекты используются очень часто, это одна из основ языка. Поэтому мы должны понять их, прежде чем углубляться куда-либо ещё.
Мы можем представить объект в виде ящика с подписанными папками. Каждый элемент данных хранится в своей папке, на которой написан ключ. По ключу папку легко найти, удалить или добавить в неё что-либо.
Пустой объект («пустой ящик») можно создать, используя один из двух вариантов синтаксиса:
let user = new Object(); // синтаксис "конструктор объекта"
let user = {}; // синтаксис "литерал объекта"
For production code requiring a large compatibility with client browsers I still suggest Ivan Nevostruev’s answer with shim to ensure Object.keys in older browsers. However, it’s possible to get the exact functionality requested using ECMA’s new defineProperty feature.
As of ECMAScript 5 — Object.defineProperty
As of ECMA5 you can use Object.defineProperty()
to define non-enumerable properties. The current compatibility
still has much to be desired, but this should eventually become usable in all browsers. ( Specifically note the current incompatibility with IE8!)
Object.defineProperty(Object.prototype, 'keys', {
value: function keys() {
var keys = [];
for(var i in this) if (this.hasOwnProperty(i)) {
keys.push(i);
}
return keys;
},
enumerable: false
});
var o = {
'a': 1,
'b': 2
}
for (var k in o) {
console.log(k, o[k])
}
console.log(o.keys())
# OUTPUT
# > a 1
# > b 2
# > ["a", "b"]
However, since ECMA5 already added Object.keys you might as well use:
Object.prototype.keys = function ()
{
var keys = [];
for(var i in this) if (this.hasOwnProperty(i))
{
keys.push(i);
}
return keys;
}
Edit: Since this answer has been around for a while I’ll leave the above untouched. Anyone reading this should also read Ivan Nevostruev’s answer below.
There’s no way of making prototype functions non-enumerable which leads to them always turning up in for-in loops that don’t use hasOwnProperty . I still think this answer would be ideal if extending the prototype of Object wasn’t so messy.
на : set.keys() set.values() Map Как объект set.entries() сравнивает ключи
[значение, значение] Чтобы сравнивать ключи, объект
использует алгоритм SameValueZero
. Это почти такое же сравнение, что и , с той лишь разницей, что let user = { // объект name: "John", // под ключом "name" хранится значение "John" age: 30 // под ключом "age" хранится значение 30 }; считается равным . Так что также может использоваться в качестве ключа. Map
Этот алгоритм не может быть заменён или модифицирован.Каждый вызов "name" возвращает объект map, так что мы можем объединить вызовы в цепочку: "John" "age" Object. entries При создании Map мы можем указать массив (или другой итерируемый объект) с парами ключ-значение для инициализации, как здесь:
// массив пар [ключ, значение]
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]); alert( map.get('1') ); // str1 Если у нас уже есть обычный объект, и мы хотели бы создать Map из него, то поможет встроенный метод Object.entries(obj) , который получает объект и возвращает массив пар ключ-значение для него, как раз в этом формате. Так что мы можем создать Map из обычного объекта следующим образом:
let obj = {
name: "John",
age: 30
}; let map = new Map(Object.entries(obj)); alert( map.get('name') ); // John
Квадратные скобки Для свойств, имена которых состоят из нескольких слов, доступ к значению «через точку» не работает:
// это вызовет синтаксическую ошибку
user.likes birds = true Точка требует, чтобы ключ был именован по правилам именования переменных. То есть не имел пробелов, не начинался с цифры и не содержал специальные символы, кроме $ и _ . Для таких случаев существует альтернативный способ доступа к свойствам через квадратные скобки. Такой способ сработает с любым именем свойства:
let user = {}; // присваивание значения свойству
user["likes birds"] = true; // получение значения свойства
alert(user["likes birds"]); // true // удаление свойства
delete user["likes birds"]; Сейчас всё в порядке. Обратите внимание, что строка в квадратных скобках заключена в кавычки (подойдёт любой тип кавычек). Квадратные скобки также позволяют обратиться к свойству, имя которого может быть результатом выражения. Например, имя свойства может храниться в переменной:
let key = "likes birds"; // то же самое, что и user["likes birds"] = true;
user[key] = true; Здесь переменная key может быть вычислена во время выполнения кода или зависеть от пользовательского ввода. После этого мы используем её для доступа к свойству. Это даёт нам большую гибкость.
let user = {
name: "John",
age: 30
}; let key = prompt("Что вы хотите узнать о пользователе?", "name"); // доступ к свойству через переменную
alert( user[key] ); // John (если ввели "name") Запись «через точку» такого не позволяет:
let user = {
name: "John",
age: 30
}; let key = "name";
alert( user.key ); // undefined
Вычисляемые свойства Мы можем использовать квадратные скобки в литеральной нотации для создания вычисляемого свойства .
let fruit = prompt("Какой фрукт купить?", "apple"); let bag = {
[fruit]: 5, // имя свойства будет взято из переменной fruit
}; alert( bag.apple ); // 5, если fruit="apple" По сути, пример выше работает так же, как и следующий пример:
let fruit = prompt("Какой фрукт купить?", "apple");
let bag = {}; // имя свойства будет взято из переменной fruit
bag[fruit] = 5; Мы можем использовать и более сложные выражения в квадратных скобках:
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
}; Квадратные скобки дают намного больше возможностей, чем запись через точку. Они позволяют использовать любые имена свойств и переменные, хотя и требуют более громоздких конструкций кода. Подведём итог: в большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки.
Перебор Map Для перебора коллекции Map есть 3 метода:
map.keys() – возвращает итерируемый объект по ключам,
map.values() – возвращает итерируемый объект по значениям,
map.entries() – возвращает итерируемый объект по парам вида [ключ, значение] , этот вариант используется по умолчанию в for..of .
let recipeMap = new Map([
["огурец", 500],
["помидор", 350],
["лук", 50]
]); // перебор по ключам (овощи)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // огурец, помидор, лук
} // перебор по значениям (числа)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
} // перебор по элементам в формате [ключ, значение]
for (let entry of recipeMap) { // то же самое, что и recipeMap.entries()
alert(entry); // огурец,500 (и так далее)
} Используется порядок вставки В отличие от обычных объектов Object , в Map перебор происходит в том же порядке, в каком происходило добавление элементов. Кроме этого, Map имеет встроенный метод forEach , схожий со встроенным методом массивов Array :
// выполняем функцию для каждой пары (ключ, значение)
recipeMap.forEach((value, key, map) => {
alert(`${key}: ${value}`); // огурец: 500 и так далее
});
Цикл "for. in" Для перебора всех свойств объекта используется цикл for..in . Этот цикл отличается от изученного ранее цикла for(;;) .
for (key in object) {
// тело цикла выполняется для каждого свойства объекта
}
let user = {
name: "John",
age: 30,
isAdmin: true
}; for (let key in user) {
// ключи
alert( key ); // name, age, isAdmin
// значения ключей
alert( user[key] ); // John, 30, true
} Обратите внимание, что все конструкции «for» позволяют нам объявлять переменную внутри цикла, как, например, let key здесь. Кроме того, мы могли бы использовать другое имя переменной. Например, часто используется вариант "for (let prop in obj)" .
Упорядочение свойств объекта Упорядочены ли свойства объекта? Другими словами, если мы будем в цикле перебирать все свойства объекта, получим ли мы их в том же порядке, в котором мы их добавляли? Можем ли мы на это рассчитывать? Короткий ответ: свойства упорядочены особым образом: свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания. Разберёмся подробнее. В качестве примера рассмотрим объект с телефонными кодами:
let codes = {
"49": "Германия",
"41": "Швейцария",
"44": "Великобритания",
// ..,
"1": "США"
}; for (let code in codes) {
alert(code); // 1, 41, 44, 49
} Если мы делаем сайт для немецкой аудитории, то, вероятно, мы хотим, чтобы код 49 был первым. Но если мы запустим код, мы увидим совершенно другую картину:
США идёт первым
затем Швейцария и так далее. Телефонные коды идут в порядке возрастания, потому что они являются целыми числами: 1, 41, 44, 49 . Целочисленные свойства? Это что? Термин «целочисленное свойство» означает строку, которая может быть преобразована в целое число и обратно без изменений. То есть, "49" – это целочисленное имя свойства, потому что если его преобразовать в целое число, а затем обратно в строку, то оно не изменится. А вот свойства "+49" или "1.2" таковыми не являются:
// Math.trunc - встроенная функция, которая удаляет десятичную часть
alert( String(Math.trunc(Number("49"))) ); // "49", то же самое ⇒ свойство целочисленное
alert( String(Math.trunc(Number("+49"))) ); // "49", не то же самое, что "+49" ⇒ свойство не целочисленное
alert( String(Math.trunc(Number("1.2"))) ); // "1", не то же самое, что "1.2" ⇒ свойство не целочисленное
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // добавим ещё одно свойство // не целочисленные свойства перечислены в порядке создания
for (let prop in user) {
alert( prop ); // name, surname, age
} Таким образом, чтобы решить нашу проблему с телефонными кодами, мы можем схитрить, сделав коды не целочисленными свойствами. Добавления знака "+" перед каждым кодом будет достаточно.
let codes = {
"+49": "Германия",
"+41": "Швейцария",
"+44": "Великобритания",
// ..,
"+1": "США"
}; for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
} Теперь код работает так, как мы задумывали.
Итого Map – коллекция пар ключ-значение. Методы и свойства:
new Map([iterable]) – создаёт коллекцию, можно указать перебираемый объект (обычно массив) из пар [ключ,значение] для инициализации.
map.set(key, value) – записывает по ключу key значение value .
map.get(key) – возвращает значение по ключу или undefined , если ключ key отсутствует.
map.has(key) – возвращает true , если ключ key присутствует в коллекции, иначе false .
map.delete(key) – удаляет элемент по ключу key .
map.clear() – очищает коллекцию от всех элементов.
map.size – возвращает текущее количество элементов. Отличия от обычного объекта Object :
Что угодно может быть ключом, в том числе и объекты.
Есть дополнительные методы, свойство size . Set – коллекция уникальных значений, так называемое «множество». Методы и свойства:
new Set(iterable) – создаёт Set , можно указать перебираемый объект со значениями для инициализации.
set.add(value) – добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект set .
set.delete(value) – удаляет значение, возвращает true если value было в множестве на момент вызова, иначе false .
set.has(value) – возвращает true , если значение присутствует в множестве, иначе false .
set.clear() – удаляет все имеющиеся значения.
set.size – возвращает количество элементов в множестве. Перебор Map и Set всегда осуществляется в порядке добавления элементов, так что нельзя сказать, что это – неупорядоченные коллекции, но поменять порядок элементов или получить элемент напрямую по его номеру нельзя.
Итого Объекты – это ассоциативные массивы с рядом дополнительных возможностей. Они хранят свойства (пары ключ-значение), где:
Ключи свойств должны быть строками или символами (обычно строками).
Значения могут быть любого типа. Чтобы получить доступ к свойству, мы можем использовать:
Запись через точку: obj.property .
Квадратные скобки ":" . Квадратные скобки позволяют взять ключ из переменной, например,
. set.entries() Удаление свойства: .
Проверка существования свойства: "name" . "John" Перебор свойств объекта: цикл for .
"age" То, что мы изучали в этой главе, называется «простым объектом» («plain object») или просто . 30 В JavaScript есть много других типов объектов: // получаем свойства объекта:
alert( user.name ); // John
alert( user.age ); // 30
для хранения упорядоченных коллекций данных, [значение, значение] для хранения информации о дате и времени, Map для хранения информации об ошибке.
… и так далее. let user = { // объект name: "John", // под ключом "name" хранится значение "John" age: 30 // под ключом "age" хранится значение 30 }; У них есть свои особенности, которые мы изучим позже. Иногда люди говорят что-то вроде «тип данных Array» или «тип данных Date», но формально они не являются отдельными типами, а относятся к типу данных Object . Они лишь расширяют его различными способами. Объекты в JavaScript очень мощные. Здесь мы только немного углубились в действительно огромную тему. Мы будем плотно работать с объектами и узнаем о них больше в следующих частях учебника.
Объект, чьи собственные перечисляемые свойства будут возвращены.
Описание
Метод Object.keys возвращает массив строковых элементов, соответствующих именам перечисляемых свойств, найденных непосредственно в самом объекте. Порядок свойств такой же, как и при ручном перечислении свойств в объекте через цикл.
Примеры
arr
consoleObjectarr
// Массивоподобный объект
obj
consoleObjectobj
// Массивоподобный объект со случайным порядком ключей
an_obj
consoleObjectan_obj
// Свойство getFoo является не перечисляемым свойством
my_obj Object
foo
my_objfoo
consoleObjectmy_obj
Если вы хотите увидеть все свойства, а не только перечисляемые, смотрите метод Object.getOwnPropertyNames()
.
Примечания
В ES5, если аргумент метода не является объектом (является примитивным значением), будет выброшено исключение TypeError
. В ES2015 такой аргумент будет приведён к объекту.
Object
is not an object // код ES5
Object
// код ES2015
Полифил
Для добавления поддержки совместимого метода Object.keys в старых окружениях, которые его ещё не реализуют, скопируйте следующий кусок кода:
// From https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
Objectkeys
Objectkeys
hasOwnProperty prototypehasOwnProperty
hasDontEnumBug
dontEnums
dontEnumsLength dontEnumslength
obj
obj obj
"Object.keys called on non-object"
result
prop
i
prop obj
obj prop
resultprop
hasDontEnumBug
i i dontEnumsLength i
obj dontEnumsi
resultdontEnumsi
result
Пожалуйста, обратите внимание, что вышеприведённый код в IE7 (и, может быть, в IE8) включает и не перечисляемые ключи, если объект передаётся из другого окна.
Более простой полифил может быть найден в статье Javascript — Object.keys Browser Compatibility (англ.).
Спецификации
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
Давайте отойдём от отдельных структур данных и поговорим об их переборе вообще.
В предыдущей главе мы видели методы map.keys() , map.values() , map.entries() .
Это универсальные методы, и существует общее соглашение использовать их для структур данных. Если бы мы делали собственную структуру данных, нам также следовало бы их реализовать.
Методы поддерживаются для структур:
Map
Set
Array
Простые объекты также можно перебирать похожими методами, но синтаксис немного отличается.
Object.keys, values, entries
Для простых объектов доступны следующие методы:
Object.keys(obj) – возвращает массив ключей.
Object.values(obj) – возвращает массив значений.
Object.entries(obj) – возвращает массив пар [ключ, значение] .
Обратите внимание на различия (по сравнению с map , например):
Первое отличие в том, что мы должны вызвать Object.keys(obj) , а не obj.keys() .
Почему так? Основная причина – гибкость. Помните, что объекты являются основой всех сложных структур в JavaScript. У нас может быть объект data , который реализует свой собственный метод data.values() . И мы всё ещё можем применять к нему стандартный метод Object.values(data) .
Второе отличие в том, что методы вида Object.* возвращают «реальные» массивы, а не просто итерируемые объекты. Это в основном по историческим причинам.
Вот пример использования Object.values для перебора значений свойств в цикле:
let user = {
name: "John",
age: 30
};
// перебор значений
for (let value of Object.values(user)) {
alert(value); // John, затем 30
}
Object.keys/values/entries игнорируют символьные свойства
Обычно это удобно. Но если требуется учитывать и символьные ключи, то для этого существует отдельный метод Object.getOwnPropertySymbols , возвращающий массив только символьных ключей. Также, существует метод Reflect.ownKeys(obj) , который возвращает все ключи.
Трансформации объекта
У объектов нет множества методов, которые есть в массивах, например map , filter и других.
Если мы хотели бы их применить, то можно использовать Object.entries с последующим вызовом Object.fromEntries :
Вызов Object.entries(obj) возвращает массив пар ключ/значение для obj .
На нём вызываем методы массива, например, map .
Используем Object.fromEntries(array) на результате, чтобы преобразовать его обратно в объект.
Например, у нас есть объект с ценами, и мы хотели бы их удвоить:
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
let doublePrices = Object.fromEntries(
// преобразовать в массив, затем map, затем fromEntries обратно объект
Object.entries(prices).map(([key, value]) => [key, value * 2])
);
alert(doublePrices.meat); // 8
Это может выглядеть сложным на первый взгляд, но становится лёгким для понимания после нескольких раз использования.
Можно делать и более сложные «однострочные» преобразования таким путём. Важно только сохранять баланс, чтобы код при этом был достаточно простым для понимания.
Ответ Анурага в целом правильный. Но поддержать Object.keys(obj) в старых браузерах вы также можете использовать приведенный ниже код, скопированный из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
. Он добавляет Object.keys(obj) метод, если он недоступен в браузере.
if (!Object.keys) {
Object.keys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
}());
}
ответ дан 29 ноя 2014 в 22:49
1 золотой значок 19 серебряных знаков 29 бронзовых знаков
Современные браузеры поддерживают:
var obj = {
key1: 'value1',
key2: 'value2',
key3: 'value3',
key4: 'value4'
}
console.log(Object.keys(obj));
// we can also get values
console.log(Object.values(obj));
В JavaScript объект — это отдельная сущность со свойствами и типом.
Поскольку обе функции возвращают массив, вы можете получить длину ключей или значений, используя свойство длины. For instance — Object .values(obj).length or Object .keys(obj).length
answered Aug 31, 2019 at 7:24
function listKeys(jObj){
var keyString = '';
for(var k in jObj){
keyString+=(','+k);
}
return keyString.slice;
}
/* listKeys({'a' : 'foo', 'b' : 'foo', 'c' : 'foo'}) -> 'a,b,c' */
answered Nov 17, 2014 at 8:34
Using ES6, you can use forEach to iterate over the Keys of an Object. To get all the keys you can use Object.keys which returns all the keys in an Object
if(props.userType){
var data = []
Object.keys(props.userType).map(i=>{
data.push(props.userType[i])
})
setService(data)
}
answered Nov 20, 2019 at 9:18
using slice, apply and join method.
var print = Array.prototype.slice.apply( obj );
alert('length='+print.length+' list'+print.join());
answered Aug 8, 2014 at 12:32
Here is solution for getting all the keys from an nested object/array.
It will recursively check for the object inside an array.
function Keys() {
let keys = [];
this.pushKey = function (key) {
keys.push(key);
};
this.getKeys = function () {
return keys;
};
}
let keys = new Keys();
let arr = [
{
a: 1,
b: {
c: [{ d: 1, e: [{ f: 1 }] }],
},
},
{
g: 1,
h: {
i: [{ j: 1, k: [{ l: 1 }] }],
},
},
];
function getObject(arr) {
for (let item of arr) {
if (Array.isArray(item)) getObject(item);
else getKeys(item);
}
}
function getKeys(obj) {
for (let key in obj) {
if (Array.isArray(obj[key])) getObject(obj[key]);
else if (typeof obj[key] === "object") getKeys(obj[key]);
keys.pushKey(key);
}
}
getObject(arr);
console.log(keys.getKeys());
answered Jan 12 at 11:37
Перебор объекта Set
Мы можем перебрать содержимое объекта set как с помощью метода for..of , так и используя forEach :
let set = new Set(["апельсин", "яблоко", "банан"]);
for (let value of set) alert(value);
// то же самое с forEach:
set.forEach((value, valueAgain, set) => {
alert(value);
});
Заметим забавную вещь. Функция в forEach у Set имеет 3 аргумента: значение value , потом снова то же самое значение valueAgain , и только потом целевой объект. Это действительно так, значение появляется в списке аргументов дважды.
Это сделано для совместимости с объектом Map , в котором колбэк forEach имеет 3 аргумента. Выглядит немного странно, но в некоторых случаях может помочь легко заменить Map на Set и наоборот.
Set имеет те же встроенные методы, что и Map :
set.keys()
– возвращает перебираемый объект для значений,
set.values()
– то же самое, что и set.keys() , присутствует для обратной совместимости с Map ,
set.entries()
– возвращает перебираемый объект для пар вида [значение, значение] , присутствует для обратной совместимости с Map .
Литералы и свойства
let user = { // объект
name: "John", // под ключом "name" хранится значение "John"
age: 30 // под ключом "age" хранится значение 30
};
У каждого свойства есть ключ (также называемый «имя» или «идентификатор»). После имени свойства следует двоеточие ":" , и затем указывается значение свойства. Если в объекте несколько свойств, то они перечисляются через запятую.
Первое свойство с именем "name" и значением "John" .
Второе свойство с именем "age" и значением 30 .
Мы можем в любой момент добавить в него новые папки, удалить папки или прочитать содержимое любой папки.
Для обращения к свойствам используется запись «через точку»:
// получаем свойства объекта:
alert( user.name ); // John
alert( user.age ); // 30
Значение может быть любого типа. Давайте добавим свойство с логическим значением:
Для удаления свойства мы можем использовать оператор delete :
Имя свойства может состоять из нескольких слов, но тогда оно должно быть заключено в кавычки:
let user = {
name: "John",
age: 30,
"likes birds": true // имя свойства из нескольких слов должно быть в кавычках
};
Последнее свойство объекта может заканчиваться запятой:
let user = {
name: "John",
age: 30,
}
Это называется «висячая запятая». Такой подход упрощает добавление, удаление и перемещение свойств, так как все строки объекта становятся одинаковыми.
Объект, объявленный как константа, может быть изменён
Объект, объявленный через const , может быть изменён.
const user = {
name: "John"
};
user.name = "Pete"; // (*)
alert(user.name); // Pete
Есть ещё один способ сделать константами свойства объекта, который мы рассмотрим в главе Флаги и дескрипторы свойств .
Set
Объект Set
– это особый вид коллекции: «множество» значений (без ключей), где каждое значение может появляться только один раз.
Его основные методы это:
new Set(iterable)
– создаёт Set , и если в качестве аргумента был предоставлен итерируемый объект (обычно это массив), то копирует его значения в новый Set .
set.add(value)
– добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект set .
set.delete(value)
– удаляет значение, возвращает true , если value было в множестве на момент вызова, иначе false .
set.has(value)
– возвращает true , если значение присутствует в множестве, иначе false .
set.clear()
– удаляет все имеющиеся значения.
set.size
– возвращает количество элементов в множестве.
Основная «изюминка» – это то, что при повторных вызовах set.add() с одним и тем же значением ничего не происходит, за счёт этого как раз и получается, что каждое значение появляется один раз.
Например, мы ожидаем посетителей, и нам необходимо составить их список. Но повторные визиты не должны приводить к дубликатам. Каждый посетитель должен появиться в списке только один раз.
Множество Set – как раз то, что нужно для этого:
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// считаем гостей, некоторые приходят несколько раз
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set хранит только 3 уникальных значения
alert(set.size); // 3
for (let user of set) {
alert(user.name); // John (потом Pete и Mary)
}
Альтернативой множеству Set может выступать массив для хранения гостей и дополнительный код для проверки уже имеющегося элемента с помощью arr.find . Но в этом случае будет хуже производительность, потому что arr.find проходит весь массив для проверки наличия элемента. Множество Set лучше оптимизировано для добавлений, оно автоматически проверяет на уникальность.
Задачи
Есть объект salaries с произвольным количеством свойств, содержащих заработные платы.
Напишите функцию sumSalaries(salaries) , которая возвращает сумму всех зарплат с помощью метода Object.values и цикла for..of .
Если объект salaries пуст, то результат должен быть 0 .
function sumSalaries(salaries) {
let sum = 0;
for (let salary of Object.values(salaries)) {
sum += salary;
}
return sum; // 650
}
let salaries = {
"John": 100,
"Pete": 300,
"Mary": 250
};
alert( sumSalaries(salaries) ); // 650
Или, как вариант, мы можем получить сумму, используя методы Object.values и reduce :
// reduce перебирает массив значений salaries,
// складывает их
// и возвращает результат
function sumSalaries(salaries) {
return Object.values(salaries).reduce((a, b) => a + b, 0) // 650
}
Открыть решение с тестами в песочнице.
Напишите функцию count(obj) , которая возвращает количество свойств объекта:
let user = {
name: 'John',
age: 30
};
alert( count(user) ); // 2
Постарайтесь сделать код как можно короче.
P. S. Игнорируйте символьные свойства, подсчитывайте только «обычные».
Открыть песочницу с тестами для задачи.
Свойство из переменной
В реальном коде часто нам необходимо использовать существующие переменные как значения для свойств с тем же именем.
function makeUser(name, age) {
return {
name: name,
age: age
// ...другие свойства
};
}
let user = makeUser("John", 30);
alert(user.name); // John
В примере выше название свойств name и age совпадают с названиями переменных, которые мы подставляем в качестве значений этих свойств. Такой подход настолько распространён, что существуют специальные короткие свойства для упрощения этой записи.
Вместо name:name мы можем написать просто name :
"age"
Мы можем использовать как обычные свойства, так и короткие в одном и том же объекте: Map
let user = { // объект
name: "John", // под ключом "name" хранится значение "John"
age: 30 // под ключом "age" хранится значение 30
}; Ограничения на имена свойств
":" Как мы уже знаем, имя переменной не может совпадать с зарезервированными словами, такими как «for», «let», «return» и т.д. Но для свойств объекта такого ограничения нет:
"John""name"
Иными словами, нет никаких ограничений к именам свойств. Они могут быть в виде строк или символов (специальный тип для идентификаторов, который будет рассмотрен позже).
30 Все другие типы данных будут автоматически преобразованы к строке. Например, если использовать число
в качестве ключа, то оно превратится в строку
:
// получаем свойства объекта:
alert( user.name ); // John
alert( user.age ); // 30 Есть небольшой подводный камень, связанный со специальным свойством
. Мы не можем установить его в необъектное значение: Map
Как мы видим, присвоение примитивного значения set.keys()
игнорируется.
Мы более подробно исследуем особенности свойства
в следующих главах set.values() Прототипное наследование set.keys()
, а также предложим Map способы исправления
такого поведения.
Проверка существования свойства, оператор «in» В отличие от многих других языков, особенность JavaScript-объектов в том, что можно получить доступ к любому свойству. Даже если свойства не существует – ошибки не будет! set.entries()[значение, значение] При обращении к свойству, которого нет, возвращается
. Это позволяет просто проверить существование свойства:
let user = {};
alert( user.noSuchProperty === undefined ); // true означает "свойства нет"
Также существует специальный оператор "in" для проверки существования свойства в объекте.
let user = { name: "John", age: 30 };
alert( "age" in user ); // true, user.age существует
alert( "blabla" in user ); // false, user.blabla не существует
Обратите внимание, что слева от оператора in должно быть имя свойства . Обычно это строка в кавычках.
Если мы опускаем кавычки, это значит, что мы указываем переменную, в которой находится имя свойства. Например:
let user = { age: 30 };
let key = "age";
alert( key in user ); // true, имя свойства было взято из переменной key
Для чего вообще нужен оператор in ? Разве недостаточно сравнения с undefined ?
В большинстве случаев прекрасно сработает сравнение с undefined . Но есть особый случай, когда оно не подходит и нужно использовать "in" .
Это когда свойство существует, но содержит значение undefined :
let obj = {
test: undefined
};
alert( obj.test ); // выведет undefined, значит свойство не существует?
alert( "test" in obj ); // true, свойство существует!
В примере выше свойство obj.test технически существует в объекте. Оператор in сработал правильно.
Подобные ситуации случаются очень редко, так как undefined обычно явно не присваивается. Для «неизвестных» или «пустых» свойств мы используем значение null .
Object. fromEntries
Мы только что видели, как создать Map из обычного объекта при помощи Object.entries(obj) .
Мы можем использовать Object.fromEntries , чтобы получить обычный объект из Map .
К примеру, у нас данные в Map , но их нужно передать в сторонний код, который ожидает обычный объект.
Вот как это сделать:
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // создаём обычный объект (*)
// готово!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
Вызов map.entries() возвращает итерируемый объект пар ключ/значение, как раз в нужном формате для Object.fromEntries .
Мы могли бы написать строку (*) ещё короче:
let obj = Object.fromEntries(map); // убрать .entries()
Это то же самое, так как Object.fromEntries ожидает перебираемый объект в качестве аргумента, не обязательно массив. А перебор map как раз возвращает пары ключ/значение, так же, как и map.entries() . Так что в итоге у нас будет обычный объект с теми же ключами/значениями, что и в map .
Map
Map – это коллекция ключ/значение, как и Object . Но основное отличие в том, что Map позволяет использовать ключи любого типа.
Методы и свойства:
new Map()
– создаёт коллекцию.
map.set(key, value)
– записывает по ключу key значение value .
map.get(key)
– возвращает значение по ключу или undefined , если ключ key отсутствует.
map.has(key)
– возвращает true , если ключ key присутствует в коллекции, иначе false .
map.delete(key)
– удаляет элемент (пару «ключ/значение») по ключу key .
map.clear()
– очищает коллекцию от всех элементов.
map.size
– возвращает текущее количество элементов.
":"set.keys() Как мы видим, в отличие от объектов, ключи не были приведены к строкам. Можно использовать любые типы данных для ключей.
30 Поэтому нам следует использовать методы
:
,
и так далее.
Map может использовать объекты в качестве ключей.
// получаем свойства объекта: alert( user.name ); // John alert( user.age ); // 30
Set Использование объектов в качестве ключей – одна из наиболее заметных и важных функций
. Это то что невозможно для
. Строка в качестве ключа в
– это нормально, но мы не можем использовать другой
в качестве ключа в . Map
Давайте попробуем заменить
на :
set.keys() set.values()
Map
Как объект set.entries() сравнивает ключи
[значение, значение] Чтобы сравнивать ключи, объект
использует алгоритм
SameValueZero
. Это почти такое же сравнение, что и
, с той лишь разницей, что let user = { // объект name: "John", // под ключом "name" хранится значение "John" age: 30 // под ключом "age" хранится значение 30 }; считается равным
. Так что
также может использоваться в качестве ключа. Map
Этот алгоритм не может быть заменён или модифицирован.
Каждый вызов "name" возвращает объект map, так что мы можем объединить вызовы в цепочку:
"John"
"age" Object. entries
При создании Map мы можем указать массив (или другой итерируемый объект) с парами ключ-значение для инициализации, как здесь:
// массив пар [ключ, значение]
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
Если у нас уже есть обычный объект, и мы хотели бы создать Map из него, то поможет встроенный метод Object.entries(obj) , который получает объект и возвращает массив пар ключ-значение для него, как раз в этом формате.
Так что мы можем создать Map из обычного объекта следующим образом:
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
Квадратные скобки
Для свойств, имена которых состоят из нескольких слов, доступ к значению «через точку» не работает:
// это вызовет синтаксическую ошибку
user.likes birds = true
Точка требует, чтобы ключ был именован по правилам именования переменных. То есть не имел пробелов, не начинался с цифры и не содержал специальные символы, кроме $ и _ .
Для таких случаев существует альтернативный способ доступа к свойствам через квадратные скобки. Такой способ сработает с любым именем свойства:
let user = {};
// присваивание значения свойству
user["likes birds"] = true;
// получение значения свойства
alert(user["likes birds"]); // true
// удаление свойства
delete user["likes birds"];
Сейчас всё в порядке. Обратите внимание, что строка в квадратных скобках заключена в кавычки (подойдёт любой тип кавычек).
Квадратные скобки также позволяют обратиться к свойству, имя которого может быть результатом выражения. Например, имя свойства может храниться в переменной:
let key = "likes birds";
// то же самое, что и user["likes birds"] = true;
user[key] = true;
Здесь переменная key может быть вычислена во время выполнения кода или зависеть от пользовательского ввода. После этого мы используем её для доступа к свойству. Это даёт нам большую гибкость.
let user = {
name: "John",
age: 30
};
let key = prompt("Что вы хотите узнать о пользователе?", "name");
// доступ к свойству через переменную
alert( user[key] ); // John (если ввели "name")
Запись «через точку» такого не позволяет:
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ); // undefined
Вычисляемые свойства
Мы можем использовать квадратные скобки в литеральной нотации для создания вычисляемого свойства .
let fruit = prompt("Какой фрукт купить?", "apple");
let bag = {
[fruit]: 5, // имя свойства будет взято из переменной fruit
};
alert( bag.apple ); // 5, если fruit="apple"
По сути, пример выше работает так же, как и следующий пример:
let fruit = prompt("Какой фрукт купить?", "apple");
let bag = {};
// имя свойства будет взято из переменной fruit
bag[fruit] = 5;
Мы можем использовать и более сложные выражения в квадратных скобках:
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
Квадратные скобки дают намного больше возможностей, чем запись через точку. Они позволяют использовать любые имена свойств и переменные, хотя и требуют более громоздких конструкций кода.
Подведём итог: в большинстве случаев, когда имена свойств известны и просты, используется запись через точку. Если же нам нужно что-то более сложное, то мы используем квадратные скобки.
Перебор Map
Для перебора коллекции Map есть 3 метода:
map.keys()
– возвращает итерируемый объект по ключам,
map.values()
– возвращает итерируемый объект по значениям,
map.entries()
– возвращает итерируемый объект по парам вида [ключ, значение] , этот вариант используется по умолчанию в for..of .
let recipeMap = new Map([
["огурец", 500],
["помидор", 350],
["лук", 50]
]);
// перебор по ключам (овощи)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // огурец, помидор, лук
}
// перебор по значениям (числа)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// перебор по элементам в формате [ключ, значение]
for (let entry of recipeMap) { // то же самое, что и recipeMap.entries()
alert(entry); // огурец,500 (и так далее)
}
Используется порядок вставки
В отличие от обычных объектов Object , в Map перебор происходит в том же порядке, в каком происходило добавление элементов.
Кроме этого, Map имеет встроенный метод forEach , схожий со встроенным методом массивов Array :
// выполняем функцию для каждой пары (ключ, значение)
recipeMap.forEach((value, key, map) => {
alert(`${key}: ${value}`); // огурец: 500 и так далее
});
Цикл "for. in"
Для перебора всех свойств объекта используется цикл for..in . Этот цикл отличается от изученного ранее цикла for(;;) .
for (key in object) {
// тело цикла выполняется для каждого свойства объекта
}
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// ключи
alert( key ); // name, age, isAdmin
// значения ключей
alert( user[key] ); // John, 30, true
}
Обратите внимание, что все конструкции «for» позволяют нам объявлять переменную внутри цикла, как, например, let key здесь.
Кроме того, мы могли бы использовать другое имя переменной. Например, часто используется вариант "for (let prop in obj)" .
Упорядочение свойств объекта
Упорядочены ли свойства объекта? Другими словами, если мы будем в цикле перебирать все свойства объекта, получим ли мы их в том же порядке, в котором мы их добавляли? Можем ли мы на это рассчитывать?
Короткий ответ: свойства упорядочены особым образом: свойства с целочисленными ключами сортируются по возрастанию, остальные располагаются в порядке создания. Разберёмся подробнее.
В качестве примера рассмотрим объект с телефонными кодами:
let codes = {
"49": "Германия",
"41": "Швейцария",
"44": "Великобритания",
// ..,
"1": "США"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
}
Если мы делаем сайт для немецкой аудитории, то, вероятно, мы хотим, чтобы код 49 был первым.
Но если мы запустим код, мы увидим совершенно другую картину:
США
идёт первым
затем Швейцария
и так далее.
Телефонные коды идут в порядке возрастания, потому что они являются целыми числами: 1, 41, 44, 49 .
Целочисленные свойства? Это что?
Термин «целочисленное свойство» означает строку, которая может быть преобразована в целое число и обратно без изменений.
То есть, "49" – это целочисленное имя свойства, потому что если его преобразовать в целое число, а затем обратно в строку, то оно не изменится. А вот свойства "+49" или "1.2" таковыми не являются:
// Math.trunc - встроенная функция, которая удаляет десятичную часть
alert( String(Math.trunc(Number("49"))) ); // "49", то же самое ⇒ свойство целочисленное
alert( String(Math.trunc(Number("+49"))) ); // "49", не то же самое, что "+49" ⇒ свойство не целочисленное
alert( String(Math.trunc(Number("1.2"))) ); // "1", не то же самое, что "1.2" ⇒ свойство не целочисленное
let user = {
name: "John",
surname: "Smith"
};
user.age = 25; // добавим ещё одно свойство
// не целочисленные свойства перечислены в порядке создания
for (let prop in user) {
alert( prop ); // name, surname, age
}
Таким образом, чтобы решить нашу проблему с телефонными кодами, мы можем схитрить, сделав коды не целочисленными свойствами. Добавления знака "+" перед каждым кодом будет достаточно.
let codes = {
"+49": "Германия",
"+41": "Швейцария",
"+44": "Великобритания",
// ..,
"+1": "США"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
}
Теперь код работает так, как мы задумывали.
Итого
Map
– коллекция пар ключ-значение.
Методы и свойства:
new Map([iterable])
– создаёт коллекцию, можно указать перебираемый объект (обычно массив) из пар [ключ,значение] для инициализации.
map.set(key, value)
– записывает по ключу key значение value .
map.get(key)
– возвращает значение по ключу или undefined , если ключ key отсутствует.
map.has(key)
– возвращает true , если ключ key присутствует в коллекции, иначе false .
map.delete(key)
– удаляет элемент по ключу key .
map.clear()
– очищает коллекцию от всех элементов.
map.size
– возвращает текущее количество элементов.
Отличия от обычного объекта Object :
Что угодно может быть ключом, в том числе и объекты.
Есть дополнительные методы, свойство size .
Set
– коллекция уникальных значений, так называемое «множество».
Методы и свойства:
new Set(iterable)
– создаёт Set , можно указать перебираемый объект со значениями для инициализации.
set.add(value)
– добавляет значение (если оно уже есть, то ничего не делает), возвращает тот же объект set .
set.delete(value)
– удаляет значение, возвращает true если value было в множестве на момент вызова, иначе false .
set.has(value)
– возвращает true , если значение присутствует в множестве, иначе false .
set.clear()
– удаляет все имеющиеся значения.
set.size
– возвращает количество элементов в множестве.
Перебор Map и Set всегда осуществляется в порядке добавления элементов, так что нельзя сказать, что это – неупорядоченные коллекции, но поменять порядок элементов или получить элемент напрямую по его номеру нельзя.
Итого
Объекты – это ассоциативные массивы с рядом дополнительных возможностей.
Они хранят свойства (пары ключ-значение), где:
Ключи свойств должны быть строками или символами (обычно строками).
Значения могут быть любого типа.
Чтобы получить доступ к свойству, мы можем использовать:
Запись через точку: obj.property .
Квадратные скобки ":" . Квадратные скобки позволяют взять ключ из переменной, например, .
set.entries()
Удаление свойства:
.
Проверка существования свойства: "name" .
"John" Перебор свойств объекта: цикл for
.
"age" То, что мы изучали в этой главе, называется «простым объектом» («plain object») или просто
. 30
В JavaScript есть много других типов объектов:
// получаем свойства объекта:
alert( user.name ); // John
alert( user.age ); // 30
для хранения упорядоченных коллекций данных,
[значение, значение] для хранения информации о дате и времени,
Map
для хранения информации об ошибке.
… и так далее.
let user = { // объект name: "John", // под ключом "name" хранится значение "John" age: 30 // под ключом "age" хранится значение 30 }; У них есть свои особенности, которые мы изучим позже. Иногда люди говорят что-то вроде «тип данных Array» или «тип данных Date», но формально они не являются отдельными типами, а относятся к типу данных Object . Они лишь расширяют его различными способами.
Объекты в JavaScript очень мощные. Здесь мы только немного углубились в действительно огромную тему. Мы будем плотно работать с объектами и узнаем о них больше в следующих частях учебника.