for...in

Інструкція for...in ("для кожного ... у") ітерує всі перелічувані рядкові властивості об'єкта (ігноруючи ті, ключами яких є символи), в тому числі успадковані перелічувані властивості.

Спробуйте його в дії

Синтаксис

for (variable in object)
  statement

Параметри

variable

На кожній ітерації отримує ім'я рядкової властивості. Може бути або оголошенням з const, let чи var, або ціллю присвоєння (наприклад, заздалегідь оголошеною змінною, властивістю об'єкта чи патерном присвоєння з деструктуруванням). Змінні, оголошені з var, не є локальними щодо циклу, тобто перебувають в тій же області видимості, що й цикл for...in.

object

Об'єкт, чиї перелічувані властивості з несимвольними ключами перебираються.

statement

Інструкція, що виконається на кожній ітерації. Може звертатися до variable. Для виконання декількох інструкцій можна використати блокову інструкцію.

Опис

Цикл опрацює всі власні перелічувані властивості об'єкта, а також ті, що успадковуються об'єктом від його прототипного ланцюжка (властивості ближчих прототипів мають перевагу над властивостями тих прототипів, які знаходяться далі від об'єкта у ланцюжку прототипів).

Цикл 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 обробляє ключі властивості в наступний спосіб:

  1. Спершу отримує всі власні рядкові ключі поточного об'єкта, в спосіб, вельми подібний до Object.getOwnPropertyNames().
  2. Для кожного ключа, якщо ніколи не оброблявся рядок з таким же значенням, отримується дескриптор властивості, і властивість обробляється лише в тому разі, якщо вона є перелічуваною. Проте цей рядок властивості буде позначено як оброблений, навіть якщо він не є перелічуваним.
  3. Потім поточний об'єкт замінюється його прототипом, і процес повторюється.

Це означає, що:

  • Будь-яка властивість, додана до поточного оброблюваного об'єкта під час ітерації, не буде оброблена, тому що всі власні властивості поточного об'єкта зберігаються наперед.
  • Якщо кілька об'єктів у ланцюжку прототипів мають властивість з однаковим іменем, то лише перша з них буде врахована, і вона обробляється лише в тому разі, якщо є перелічуваною. Якщо вона неперелічувана, то жодні інші властивості з таким же іменем далі за ланцюжком прототипів не обробляються, навіть якщо є перелічуваними.

Загалом, найкраще не додавати, не змінювати та не прибирати властивості з об'єктів під час ітерування, крім поточної властивості, що обробляється. Специфікація явно дозволяє реалізаціям не дотримуватися алгоритму, описаному вище, в наступних випадках:

  • Ланцюжок прототипів об'єкта змінюється під час ітерування.
  • Властивість видаляється з об'єкта або його ланцюжка прототипів під час ітерування.
  • Властивість додається до ланцюжка прототипів під час ітерування.
  • Перелічуваність властивості змінюється під час ітерування.

У цих випадках реалізації можуть поводитися інакше, ніж можна було б очікувати, або навіть одна від одної.

Ітерування масиву і цикл 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
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
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

Дивіться також