for...in
Інструкція for...in
("для кожного ... у") ітерує всі перелічувані рядкові властивості об'єкта (ігноруючи ті, ключами яких є символи), в тому числі успадковані перелічувані властивості.
Спробуйте його в дії
Синтаксис
for (variable in object)
statement
Параметри
variable
На кожній ітерації отримує ім'я рядкової властивості. Може бути або оголошенням з
const
,let
чиvar
, або ціллю присвоєння (наприклад, заздалегідь оголошеною змінною, властивістю об'єкта чи патерном присвоєння з деструктуруванням). Змінні, оголошені зvar
, не є локальними щодо циклу, тобто перебувають в тій же області видимості, що й циклfor...in
.object
Об'єкт, чиї перелічувані властивості з несимвольними ключами перебираються.
statement
Інструкція, що виконається на кожній ітерації. Може звертатися до
variable
. Для виконання декількох інструкцій можна використати блокову інструкцію.
Опис
Цикл опрацює всі власні перелічувані властивості об'єкта, а також ті, що успадковуються об'єктом від його прототипного ланцюжка (властивості ближчих прототипів мають перевагу над властивостями тих прототипів, які знаходяться далі від об'єкта у ланцюжку прототипів).
Як і у випадку інших інструкцій циклів, всередині statement
можна користуватися інструкціями керування плином виконання:
break
зупиняє виконанняstatement
і переходить до першої інструкції після циклу.continue
зупиняє виконанняstatement
і переходить до наступної ітерації циклу.
Цикл for...in
перебирає лише перелічувані несимвольні властивості. Об'єкти, які створюються за допомогою таких вбудованих конструкторів, як Array
та Object
, мають неперелічувані властивості, успадковані від Array.prototype
та Object.prototype
, як от метод indexOf()
об'єкта Array
чи метод toString()
об'єкта Object
. Такі властивості не оброблятимуться циклом for...in
.
Порядок перебирання властивостей, згідно зі сучасною специфікацією ECMAScript — чітко визначений, і незмінний між реалізаціями. Всередині кожного компонента прототипного ланцюжка всі ключі, які є додатними цілими числами (зазвичай це індекси масивів) опрацьовуються першими, в порядку зростання числового значення. Потім опрацьовуються інші рядкові ключі, в хронологічному порядку відносно дати створення кожної властивості.
Частина for...in
variable
приймає що завгодно, що може стояти зліва оператора =
. Якщо не робити присвоєння змінній всередині тіла циклу, то можна для її оголошення використати const
(таке присвоєння не вплине на наступну ітерацію, адже в ній буде геть окрема змінна). Інакше – можна використати let
. Деструктурування можна використати для присвоєння кількох локальних змінних, або використати аксесор властивості, як от for (x.y of iterable)
, щоб присвоїти значення властивості об'єкта.
Застарілий синтаксис дозволяє оголошенням var
змінної циклу мати ініціалізатор. Це викидає синтаксичну помилку в строгому режимі й ігнорується в нестрогому.
Видалені, додані чи змінені властивості
Цикл for...in
обробляє ключі властивості в наступний спосіб:
- Спершу отримує всі власні рядкові ключі поточного об'єкта, в спосіб, вельми подібний до
Object.getOwnPropertyNames()
. - Для кожного ключа, якщо ніколи не оброблявся рядок з таким же значенням, отримується дескриптор властивості, і властивість обробляється лише в тому разі, якщо вона є перелічуваною. Проте цей рядок властивості буде позначено як оброблений, навіть якщо він не є перелічуваним.
- Потім поточний об'єкт замінюється його прототипом, і процес повторюється.
Це означає, що:
- Будь-яка властивість, додана до поточного оброблюваного об'єкта під час ітерації, не буде оброблена, тому що всі власні властивості поточного об'єкта зберігаються наперед.
- Якщо кілька об'єктів у ланцюжку прототипів мають властивість з однаковим іменем, то лише перша з них буде врахована, і вона обробляється лише в тому разі, якщо є перелічуваною. Якщо вона неперелічувана, то жодні інші властивості з таким же іменем далі за ланцюжком прототипів не обробляються, навіть якщо є перелічуваними.
Загалом, найкраще не додавати, не змінювати та не прибирати властивості з об'єктів під час ітерування, крім поточної властивості, що обробляється. Специфікація явно дозволяє реалізаціям не дотримуватися алгоритму, описаному вище, в наступних випадках:
- Ланцюжок прототипів об'єкта змінюється під час ітерування.
- Властивість видаляється з об'єкта або його ланцюжка прототипів під час ітерування.
- Властивість додається до ланцюжка прототипів під час ітерування.
- Перелічуваність властивості змінюється під час ітерування.
У цих випадках реалізації можуть поводитися інакше, ніж можна було б очікувати, або навіть одна від одної.
Ітерування масиву і цикл for...in
Індекси масиву — це лише перелічувані властивості, які у всьому ідентичні властивостям звичайних об'єктів, окрім того, що їхні імена — це цілі числа. Цикл for...in
обробить всі ключі, які є цілими числами, перед початком обробки інших ключів, чітко у напрямку їхнього зростання, що робить поведінку інструкції for...in
близькою до звичайного перебирання масиву. Проте, цикл for...in
також поверне всі перелічувані властивості, включно з успадкованими й тими, чиї ключі не є цілими числами. На відміну від for...of
, for...in
використовує перелічування властивостей замість ітератора масиву. У розріджених масивах for...of
буде обробляти порожні гнізда, а for...in
— ні.
Краще використовувати цикл for
з числовим індексом, Array.prototype.forEach()
чи цикл for...of
, оскільки вони повертатимуть індекс як число, а не як рядок, а також уникатимуть властивостей поза індексом.
Перебирання лише власних властивостей об'єкта
В разі, якщо необхідно розглянути лише ті властивості, які приєднані до самого об'єкта, без урахування його прототипів, можна застосувати один із наступних підходів:
Object.keys
поверне список власних перелічуваних властивостей із рядковими ключами, а от Object.getOwnPropertyNames
також міститиме неперелічувані властивості.
Багато настанов щодо стилю та лінтерів у JavaScript рекомендують не застосовувати for...in
через те, що він перебирає весь ланцюжок прототипів, а це рідко є бажаним. Також його часто плутають із більш вживаним циклом for...of
. Найбільш доцільним може бути його використання для потреб зневадження, як простого способу перевірити властивості об'єкта (шляхом виведення їх у консоль, або ж інакшим чином). В ситуаціях, коли об'єкти використовуються як утилітарні пари ключ-значення, for...in
дає змогу перевірити, чи зберігає якийсь з таких ключів певне значення.
Приклади
Застосування for...in
Цикл for...in
, який наведено нижче, перевіряє всі перелічувані, несимвольні властивості об'єкта, і для кожної друкує рядок з іменем властивості та її значенням.
const obj = { a: 1, b: 2, c: 3 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
// Друкує:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"
Перебирання власних властивостей об'єкта
Наступна функція демонструє використання методу Object.hasOwn()
: успадковані властивості не виводяться.
const triangle = { a: 1, b: 2, c: 3 };
function ColoredTriangle() {
this.color = "red";
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (const prop in obj) {
if (Object.hasOwn(obj, prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
// Друкує:
// "obj.color = red"
Внесення змін паралельно
Застереження: Подібний код не повинен писатися. Він доданий тут лише для ілюстрування поведінки
for...in
у деяких крайніх випадках.
Властивості, додані до поточного об'єкта під час ітерування, ніколи не обробляються:
const obj = { a: 1, b: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
obj.c = 3;
}
// Виводить:
// obj.a = 1
// obj.b = 2
Затулені властивості обробляються лише раз:
const proto = { a: 1 };
const obj = { __proto__: proto, a: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
// Виводить:
// obj.a = 2
Object.defineProperty(obj, "a", { enumerable: false });
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
// Нічого не виводить, адже перша властивість "a" обробляється як неперелічувана.
На додачу – погляньмо на наступні випадки, в яких поведінка не визначена, і реалізації зазвичай розходяться від заданого алгоритму:
Змінювання прототипу під час ітерування:
const obj = { a: 1, b: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
Object.setPrototypeOf(obj, { c: 3 });
}
Видалення властивості під час ітерування:
const obj = { a: 1, b: 2, c: 3 };
// Видалення властивості до її обробки
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
delete obj.c;
}
const obj2 = { a: 1, b: 2, c: 3 };
// Видалення властивості після її обробки
for (const prop in obj2) {
console.log(`obj2.${prop} = ${obj2[prop]}`);
delete obj2.a;
}
Перелічувані властивості додаються до прототипу під час ітерування:
const proto = {};
const obj = { __proto__: proto, a: 1, b: 2 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
proto.c = 3;
}
Змінювання перелічуваності властивості під час ітерування:
const obj = { a: 1, b: 2, c: 3 };
for (const prop in obj) {
console.log(`obj.${prop} = ${obj[prop]}`);
Object.defineProperty(obj, "c", { enumerable: false });
}
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
for...in
|
Chrome Full support 1 | Edge Full support 12 | Firefox Full support 1 | Internet Explorer Full support 3 | Opera Full support 2 | Safari Full support 1 | WebView Android Full support 1 | Chrome Android Full support 18 | Firefox for Android Full support 4 | Opera Android Full support 10.1 | Safari on iOS Full support 1 | Samsung Internet Full support 1.0 | Deno Full support 1.0 | Node.js Full support 0.10.0 |