for...in

Обходим имена перечисляемых свойств объекта.

Время чтения: меньше 5 мин

Кратко

Скопировано

Инструкция for...in выполняет цикл обхода перечисляемых свойств объекта, включая перечисляемые свойства прототипов.

В каждой итерации цикла переменная цикла получает значение, соответствующее имени перечисляемого свойства объекта. Порядок обхода свойств строго определён спецификацией ECMAScript.

Перечисляемые свойства — это свойства, которые разработчик добавляет объекту.

В цикл for...in не попадут: встроенные свойства, например, методы объекта, унаследованные от Object.prototype, а также свойства, имена которых имеют тип Symbol.

Как пишется

Скопировано

Схематично структура для создания цикла выглядит так:

        
          
          for (переменная in объект) {  // Действия внутри цикла}
          for (переменная in объект) {
  // Действия внутри цикла
}

        
        
          
        
      

Для цикла необходимо объявить название переменной и указать сам объект, свойства которого нужно обойти. В объявленной переменной будет храниться имя свойства во время итерации:

        
          
          const cat = {  name: 'Борис',  color: 'red',  age: 8}for (const key in cat) {  console.log(`${key} — ${cat[key]}`)}// name — 'Борис',// color — 'red',// age — 8
          const cat = {
  name: 'Борис',
  color: 'red',
  age: 8
}

for (const key in cat) {
  console.log(`${key}${cat[key]}`)
}
// name — 'Борис',
// color — 'red',
// age — 8

        
        
          
        
      

Как понять

Скопировано

Цикл for...in — это хороший способ пройти по всем свойствам объекта, но стоит помнить несколько важных особенностей.

Что такое перечисляемые свойства

Скопировано

Перечисляемые свойства объекта — это свойства, которые явно помечены такими. Сказать свойству, что оно перечисляемое, можно через специальный метод defineProperty(). Для простоты все свойства, которые добавляются к объекту, являются перечисляемыми по умолчанию. Встроенные свойства не перечисляются. Например, метод indexOf() у объекта String или метод toString() у любого объекта не участвует в цикле for...in.

Цикл for...in будет перебирать не только собственные свойства объекта, но и перечисляемые свойства, наследуемые от цепочки прототипов. Имена свойств в цикле не повторяются. Свойства объекта имеют наивысший приоритет, а свойства ближайших прототипов — больший приоритет над свойствами прототипов, находящихся дальше от объекта в его цепочке прототипов:

        
          
          const grandParent = { a: 1, b: 2 }const parent = { b: 3, c: 4 }const object = { c: 5 }Object.setPrototypeOf(parent, grandParent)Object.setPrototypeOf(object, parent)for (const key in object) {  console.log(key, object[key])}// c 5// b 3// a 1
          const grandParent = { a: 1, b: 2 }
const parent = { b: 3, c: 4 }
const object = { c: 5 }

Object.setPrototypeOf(parent, grandParent)
Object.setPrototypeOf(object, parent)

for (const key in object) {
  console.log(key, object[key])
}
// c 5
// b 3
// a 1

        
        
          
        
      

Если несколько объектов в цепочке прототипов имеют свойство с одинаковым именем, будет учитываться только первое из них, и оно будет участвовать в цикле только в том случае, если перечисляемое. Если оно неперечисляемое, никакие другие свойства с таким же именем дальше по цепочке прототипов не будут участвовать в цикле, даже если они являются перечислимыми.

Порядок перечисления свойств

Скопировано

Спецификация ECMAScript определяет следующий порядок обхода перечисляемых свойств объекта при выполнении цикла for...in:

  1. Неотрицательные целочисленные ключи (те, которые могут быть индексами массива) в порядке возрастания значений.
  2. Строковые ключи в порядке возрастания хронологии создания.

В том же порядке будут перебираться свойства прототипов объекта.

Демонстрация порядка обхода целочисленных свойств:

        
          
          const booksById = {  341: {    name: 'Harry Potter'  },  144: {    name: 'Flowers for Algernon'  },  202: {    name: 'Lord of the Rings'  }}for (const key in booksById) {  console.log(key)}// 144// 202// 341
          const booksById = {
  341: {
    name: 'Harry Potter'
  },
  144: {
    name: 'Flowers for Algernon'
  },
  202: {
    name: 'Lord of the Rings'
  }
}

for (const key in booksById) {
  console.log(key)
}
// 144
// 202
// 341

        
        
          
        
      

Демонстрация порядка обхода объекта, имеющего строковые и символьные свойства:

        
          
          const id = Symbol('id')const developer = {  name: 'Ваня',  language: 'JavaScript',  [id]: '8888',  company: 'Google'}developer.age = 33for (const key in developer) {  console.log(key)}// name// language// company// age
          const id = Symbol('id')

const developer = {
  name: 'Ваня',
  language: 'JavaScript',
  [id]: '8888',
  company: 'Google'
}

developer.age = 33

for (const key in developer) {
  console.log(key)
}
// name
// language
// company
// age

        
        
          
        
      

❗️ Использовать цикл for...in для перебора массивов не рекомендуется по нескольким причинам:

  1. В цикл for...in попадут не только целочисленные свойства, но и строковые, а также наследуемые.
  2. Переменная цикла на каждой итерации, соответствующая индексу текущего элемента, будет иметь тип строки, а не числа.

Для перебора у массива есть собственные методы: forEach(), map() и другие.

Изменение объекта во время выполнения цикла for...in

Скопировано

По возможности следует избегать изменения объекта при выполнении цикла.

Спецификация ECMAScript определяет случаи во время выполнения итерации, при которых алгоритм работы цикла может нарушаться:

  • модификация цепочки прототипов;
  • удаление свойства из объекта или из объектов в цепочке прототипов;
  • добавление свойства объекту из цепочки прототипов;
  • изменение атрибута enumerable у свойств объекта или в объектах из цепочки прототипов.

Подсказки

Скопировано

💡 Для получения списка перечисляемых свойств только самого объекта, можно использовать статический метод Object.keys().