Пошук уроків, статей та іншого контенту
Дізнайтеся, як for...in перебирає перелічувані властивості об'єкта. Розберіть особливості успадкованих властивостей і зрозумійте, чому цей цикл зазвичай не рекомендують для перебору масивів.
for...inЦикл for...in перебирає перелічувані властивості об’єкта. На кожній ітерації змінна циклу отримує ім’я властивості у вигляді рядка.
const user = {
name: "Марія",
age: 28,
role: "developer",
};
for (const property in user) {
console.log(property, user[property]);
}Результат:
name Марія
age 28
role developerЗначення властивості отримують через квадратні дужки:
const property = "name";
console.log(user[property]); // МаріяВикористання user.property у цьому випадку було б неправильним: воно шукало б властивість із буквальним ім’ям property, а не зі значенням змінної.
for...infor...in має дві важливі особливості:
Перебирає лише перелічувані властивості.
Перебирає не тільки власні властивості об’єкта, а й перелічувані властивості з його ланцюжка прототипів.
Власні властивості належать безпосередньо об’єкту:
const user = {
name: "Олег",
};
console.log(Object.hasOwn(user, "name")); // trueЯкщо властивість доступна через прототип, але не записана безпосередньо в об’єкті, вона є успадкованою:
const userPrototype = {
isActive: true,
};
const user = Object.create(userPrototype);
user.name = "Олег";
for (const property in user) {
console.log(property, user[property]);
}Результат:
name Олег
isActive trueВластивість isActive перебирається, тому що вона перелічувана і належить прототипу userPrototype.
Якщо потрібно перебрати лише властивості самого об’єкта, перевіряйте їх за допомогою Object.hasOwn():
const userPrototype = {
isActive: true,
};
const user = Object.create(userPrototype);
user.name = "Олег";
user.age = 32;
for (const property in user) {
if (Object.hasOwn(user, property)) {
console.log(property, user[property]);
}
}Результат:
name Олег
age 32Object.hasOwn(object, property) повертає true, якщо властивість є безпосередньо в об’єкті, і false, якщо вона успадкована.
Старіший варіант перевірки:
if (Object.prototype.hasOwnProperty.call(user, property)) {
// Властивість власна
}Object.hasOwn() є коротшим і безпечнішим способом.
За замовчуванням властивості, створені звичайним присвоєнням, є перелічуваними:
const product = {};
product.name = "Ноутбук";
for (const property in product) {
console.log(property); // name
}Перелічуваність можна змінити за допомогою Object.defineProperty():
const product = {
name: "Ноутбук",
};
Object.defineProperty(product, "price", {
value: 50000,
enumerable: false,
});
for (const property in product) {
console.log(property);
}Результат:
nameВластивість price існує і доступна:
console.log(product.price); // 50000Але вона не перебирається через for...in, тому що має enumerable: false.
Власні перелічувані властивості можна отримати за допомогою Object.keys():
const keys = Object.keys(product);
console.log(keys); // ["name"]for...in повертає імена властивостей у певному порядку, але не варто будувати важливу логіку на припущенні про порядок властивостей, особливо коли йдеться про успадкування.
Для звичайних об’єктів числові ключі, записані як невід’ємні цілі числа, зазвичай перебираються за зростанням, а інші ключі — у порядку додавання:
const values = {
10: "десять",
2: "два",
name: "значення",
category: "тест",
};
for (const key in values) {
console.log(key);
}Типовий результат:
2
10
name
categoryЯкщо потрібен передбачуваний список власних ключів, часто краще явно отримати його через Object.keys() і перебирати цей масив.
for...in не рекомендують для масивівМасив у JavaScript є об’єктом, тому технічно його можна перебирати через for...in:
const colors = ["синій", "жовтий", "зелений"];
for (const key in colors) {
console.log(key, colors[key]);
}Можливий результат:
0 синій
1 жовтий
2 зеленийПроте for...in повертає ключі, а не значення. Крім того, цикл може перебрати додаткові властивості масиву або властивості, успадковані з прототипу.
const numbers = [10, 20, 30];
numbers.description = "масив чисел";
for (const key in numbers) {
console.log(key, numbers[key]);
}Результат міститиме не лише індекси:
0 10
1 20
2 30
description масив чиселТому для масивів використовуйте відповідний інструмент залежно від завдання:
for...of — перебір значень;
звичайний for — перебір за індексами;
forEach() — виконання функції для кожного елемента;
Object.keys() — якщо потрібно явно перебирати ключі.
for...ofconst colors = ["синій", "жовтий", "зелений"];
for (const color of colors) {
console.log(color);
}Результат:
синій
жовтий
зеленийforconst colors = ["синій", "жовтий", "зелений"];
for (let index = 0; index < colors.length; index += 1) {
console.log(index, colors[index]);
}Object.keys()const colors = ["синій", "жовтий", "зелений"];
for (const index of Object.keys(colors)) {
console.log(index, colors[index]);
}У цьому випадку Object.keys() повертає лише власні перелічувані ключі масиву.
for...in і символиfor...in перебирає ключі, які є рядками. Властивості з ключами-символами він не повертає:
const settings = {
theme: "dark",
};
const internalKey = Symbol("internal");
settings[internalKey] = "службове значення";
for (const key in settings) {
console.log(key); // theme
}
console.log(settings[internalKey]); // службове значенняЩоб отримати власні символьні ключі, використовуйте Object.getOwnPropertySymbols():
console.log(Object.getOwnPropertySymbols(settings));
// [Symbol(internal)]Щоб отримати всі власні ключі — і рядкові, і символьні — використовуйте Reflect.ownKeys():
console.log(Reflect.ownKeys(settings));
// ["theme", Symbol(internal)]Функція нижче створює об’єкт із параметрами запиту. Вона перебирає власні властивості та пропускає успадковані:
const filtersPrototype = {
source: "catalog",
};
const filters = Object.create(filtersPrototype);
filters.category = "books";
filters.page = 2;
filters.limit = 20;
function toQueryString(object) {
const parts = [];
for (const key in object) {
if (!Object.hasOwn(object, key)) {
continue;
}
const value = object[key];
if (value === undefined || value === null) {
continue;
}
parts.push(
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
);
}
return parts.join("&");
}
console.log(toQueryString(filters));
// category=books&page=2&limit=20Властивість source не потрапила до результату, тому що вона успадкована з прототипу.
Для такого завдання також можна використати Object.entries():
const parts = [];
for (const [key, value] of Object.entries(filters)) {
if (value !== undefined && value !== null) {
parts.push(
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
);
}
}
console.log(parts.join("&"));
// category=books&page=2&limit=20Object.entries() повертає масив пар [ключ, значення] лише для власних перелічуваних властивостей.
for...inВикористовуйте, коли:
потрібно перебрати ключі об’єкта;
потрібно враховувати або явно перевіряти успадковані властивості;
працюєте з об’єктом, а не з масивом.
for (const key in object) {
if (Object.hasOwn(object, key)) {
console.log(key, object[key]);
}
}Object.keys()Повертає власні перелічувані ключі у вигляді масиву:
for (const key of Object.keys(object)) {
console.log(key, object[key]);
}Object.values()Повертає власні перелічувані значення:
for (const value of Object.values(object)) {
console.log(value);
}Object.entries()Повертає пари [ключ, значення]:
for (const [key, value] of Object.entries(object)) {
console.log(key, value);
}Для сучасного коду Object.keys(), Object.values() і Object.entries() часто роблять намір розробника зрозумілішим: перебираються саме власні властивості об’єкта.
for...inconst users = ["Анна", "Петро"];
for (const index in users) {
console.log(users[index]);
}Такий код може працювати в простому випадку, але він покладається на перебір властивостей об’єкта. Для масиву краще використати:
for (const user of users) {
console.log(user);
}const user = {
name: "Ірина",
age: 25,
};
for (const key in user) {
console.log(user.key); // undefined
}user.key шукає властивість із назвою "key". Для динамічного ключа потрібні квадратні дужки:
for (const key in user) {
console.log(user[key]);
}const prototype = {
inherited: true,
};
const object = Object.create(prototype);
object.own = true;
for (const key in object) {
console.log(key);
}Цикл виведе і own, і inherited. Якщо потрібні лише власні властивості:
for (const key in object) {
if (Object.hasOwn(object, key)) {
console.log(key);
}
}Не варто без потреби додавати або видаляти властивості об’єкта всередині for...in. Це ускладнює розуміння коду та може призвести до неочікуваної поведінки.
Якщо потрібно змінювати об’єкт, спочатку отримайте окремий список ключів:
const settings = {
debug: true,
cache: false,
};
for (const key of Object.keys(settings)) {
if (settings[key] === false) {
delete settings[key];
}
}
console.log(settings); // { debug: true }for...in перебирає імена перелічуваних властивостей об’єкта.
Ключ у циклі має рядковий тип.
Цикл може перебирати успадковані властивості з прототипу.
Для перевірки власної властивості використовуйте Object.hasOwn().
Неперелічувані властивості та властивості-символи for...in не перебирає.
Для масивів зазвичай використовуйте for...of, for, forEach() або методи Object.keys() / Object.entries().
Object.keys(), Object.values() і Object.entries() перебирають лише власні перелічувані властивості.
Під час динамічного доступу до властивості використовуйте object[key], а не object.key.