В чем разница между (для… in) и (для… of) операторов в JavaScript?


410

Я знаю, что такое for... inцикл (он повторяется по ключу), но слышал в первый раз for... of(он повторяет по значению).

Я запутался с for... ofпетлей. Я не получил adject. Это код ниже:

var arr = [3, 5, 7];
arr.foo = "hello";

for (var i in arr) {
  console.log(i); // logs "0", "1", "2", "foo"
}

for (var i of arr) {
  console.log(i); // logs "3", "5", "7"
  // it is does not log "3", "5", "7", "hello"
}

То, что я получил, for... ofперебирает значения свойств. Тогда почему он не регистрируется (возвращается) "3", "5", "7", "hello"вместо "3", "5", "7"? но for... inцикл повторяется по каждому ключу ( "0", "1", "2", "foo"). Здесь for... inцикл также перебирает fooключ. Но for... ofне итерация по стоимости fooимущества , т.е. "hello". Почему это так?

Короче говоря:

Здесь я утешаю for... ofпетлю. Это должно войти, "3", "5", "7","hello"но здесь это входит "3", "5", "7". Почему ?

Пример ссылки


1
в случае, если вы его пропустили, вот начальная ссылка developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Энтони Рассел

1
Насколько я понимаю, он for ... ofбыл переведен на язык, чтобы решить проблемы с использованием for ... inмассивов. Array.prototypeмогут быть изменены таким образом, чтобы были доступны дополнительные свойства, что делает небезопасным их повторение, поскольку вы можете получить нечисловые ключи, которые вы не ожидали.
Филогенез

2
Для будущих читателей: это, вероятно, не дубликат ключевого ofслова JavaScript (для ... циклов) , поскольку он спрашивает о конкретном поведении функции, а не запрашивает общий обзор.
Апсиллеры

2
Просто for <key> infor <value> offor..of
привыкните

Большая статья о перечислимом medium.com/@shivamethical/...
Kalhan.Toress

Ответы:


304

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

for of(новинка в ES6) использует специфичный для объекта итератор и перебирает значения, сгенерированные этим.

В вашем примере итератор массива выдает все значения в массиве (игнорируя неиндексные свойства).


9
for ... ofстандартизирован в ES6.
Джастин

2
Странно, клянусь, я где-то читал, что он был перенесен обратно в ES7, но, видимо, это было не так. Виноват.
Александр О'Мара

40
Мнемоника: 'o'f -> not' o'bjects, 'i'n -> not' i'terables
Placoplatr

4
другая мнемоника: for... of:: arrays :: массивы всегда имеют длину, так что вы можете думать for.. [n-й элемент] of.. [q-элементов]
Натан Смит

14
Еще одна мнемоника ... for..in..keys=== внешние ключи === использовать for...inдля ключей! Таким образом, используйте for...ofдля ценностей.
Гюнтер

237

Я нахожу полный ответ по адресу: https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html (хотя это и для сценария типа, то же самое относится и к javascript)

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

Вот пример, который демонстрирует это различие:

let list = [4, 5, 6];

for (let i in list) {
   console.log(i); // "0", "1", "2",
}

for (let i of list) {
   console.log(i); // "4", "5", "6"
}

Другое различие заключается в том, что forin действует на любой объект; он служит способом проверки свойств этого объекта. for..ofс другой стороны, в основном интересуются значениями итерируемых объектов. Встроенные объекты, такие как Map и Set, implement Symbol.iteratorразрешают доступ к сохраненным значениям.

let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals";

for (let pet in pets) {
   console.log(pet); // "species"
}

for (let pet of pets) {
    console.log(pet); // "Cat", "Dog", "Hamster"
}

1
Более того, вызывая что-то вроде for (let i of {}) {console.log (i); } будет выдавать TypeError: VM391: 1 Uncaught TypeError: {} не может быть повторен при <anonymous>: 1: 14, по крайней мере, в Chrome
kboom

TS за победу - пример неверный, последний должен возвращать «млекопитающих», а не // «Кошка», «Собака», «Хомяк»
martinp999

8
Я помню это по: для "в" для index. И тогда для "из" будет valuesкаждого индекса / ключа / элемента.
SherylHohman

Хорошо, это будет для меня королем: используя for-ins для итерации элементов, мне обычно приходится создавать let thisItem = items[all];переменные, это for...ofпомогает быстро!
Василий Холл

Я помню это по: for...inкак Object.keys(), угадайте, что? Массивы - это объекты, которые также возвращают свои значения. :)
Суджит Аграри

38

Для ... в цикле

Цикл for ... in устраняет недостатки цикла for, устраняя логику счета и условие выхода.

Пример:

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

Но вам все еще приходится иметь дело с проблемой использования индекса для доступа к значениям массива, и это воняет; это почти делает его более запутанным, чем раньше.

Кроме того, цикл for ... in может доставить вам большие неприятности, когда вам нужно добавить дополнительный метод в массив (или другой объект). Поскольку цикл for ... in перебирает все перечисляемые свойства, это означает, что если вы добавите какие-либо дополнительные свойства в прототип массива, то эти свойства также появятся в цикле.

Array.prototype.decimalfy = function() {
  for (let i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

Печать:

0

1

2

3

4

5

6

7

8

9

function () {for (пусть i = 0; i <this.length; i ++) {this [i] = this [i] .toFixed (2); }}

Вот почему для циклов for ... in не рекомендуется использовать циклы над массивами.

ПРИМЕЧАНИЕ . Цикл forEach - это другой тип цикла for в JavaScript. Однако на forEach()самом деле это метод массива, поэтому он может использоваться только с массивами. Также нет способа остановить или разорвать цикл forEach. Если вам нужен такой тип поведения в вашем цикле, вам придется использовать базовый цикл for.

Для ... петли

Цикл for ... of используется для циклического повторения любого типа данных.

Пример:

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

Печать:

0

1

2

3

4

5

6

7

8

9

Это делает цикл for ... of наиболее кратким вариантом всех циклов for.

Но подождите, это еще не все! Цикл for ... of также имеет некоторые дополнительные преимущества, которые устраняют недостатки циклов for и for ... in.

Вы можете остановить или прервать цикл ... в любое время.

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  if (digit % 2 === 0) {
    continue;
  }
  console.log(digit);
}

Печать:

1

3

5

7

9

И вам не нужно беспокоиться о добавлении новых свойств к объектам. Цикл for ... of будет зацикливать только значения в объекте.


2
« Цикл for ... in улучшает слабые стороны цикла for, устраняя логику подсчета и условие выхода » - нет, это не то, что он делает. Не за что.
Берги

1
@ Берджи Не могли бы вы уточнить, почему вы думаете, что это не то, что он делает, и что вы на самом деле думаете, что это улучшает?
Элар

2
Ничего не улучшается, у него есть свой смысл существования. Он делает что-то совершенно иное, чем for (var index=0; index<arr.length; index++)цикл (в indexотличие от вашего примера счетчик является целым числом).
Берги

Это сбивает с толку то, что значения массива, которые вы выбрали для примера, соответствуют значениям индекса массива ...
Сергей

20

Разница for..inи for..of:

И то, for..inи другое for..of- циклические конструкции, которые используются для перебора структур данных. Разница лишь в том, что они повторяют:

  1. for..inперебирает все перечисляемые ключи свойств объекта
  2. for..ofперебирает значения повторяемого объекта. Примерами итерируемых объектов являются массивы, строки и списки узлов.

Пример:

let arr = ['el1', 'el2', 'el3'];

arr.addedProp = 'arrProp';

// elKey are the property keys
for (let elKey in arr) {
  console.log(elKey);
}

// elValue are the property values
for (let elValue of arr) {
  console.log(elValue)
}

В этом примере мы можем наблюдать, как for..inцикл перебирает ключи объекта, который в этом примере является объектом массива. Ключи 0, 1, 2 соответствуют элементам массива, которые мы добавили, и addedProp. Вот как arrвыглядит объект массива в chrome devtools:

введите описание изображения здесь

Вы видите, что наш for..inцикл не делает ничего, кроме простой итерации по этим значениям.


for..ofПетля в нашем примере перебирает значения структуры данных. Значения в этом конкретном примере 'el1', 'el2', 'el3'. Значения, которые будет возвращать итеративная структура данных, for..ofзависят от типа итерируемого объекта. Например, массив будет возвращать значения всех элементов массива, тогда как строка возвращает каждый отдельный символ строки.


8

Оператор for...inперебирает перечисляемые свойства объекта в произвольном порядке. Перечислимыми свойствами являются те свойства, для которых внутренний флаг [[Enumerable]] установлен в значение true, поэтому, если в цепочке прототипов есть какое-либо перечислимое свойство, for...inцикл будет итерироваться и по ним.

Оператор for...ofперебирает данные, которые итерируемый объект определяет для перебора.

Пример:

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];

for (let i in iterable) {
  console.log(i); // logs: 0, 1, 2, "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs: 0, 1, 2,
  }
}

for (let i of iterable) {
  console.log(i); // logs: 3, 5, 7
}

Как и раньше, вы можете пропустить добавление hasOwnPropertyв for...ofциклы.


7

Оператор for-in перебирает перечисляемые свойства объекта в произвольном порядке.

Цикл будет перебирать все перечисляемые свойства самого объекта и тех, которые объект наследует от прототипа своего конструктора

Вы можете думать об этом как о «в» итерации и перечислять все ключи.

var str = 'abc';
var arrForOf = [];
var arrForIn = [];

for(value of str){
  arrForOf.push(value);
}

for(value in str){
  arrForIn.push(value);
}

console.log(arrForOf); 
// ["a", "b", "c"]
console.log(arrForIn); 
// ["0", "1", "2", "formatUnicorn", "truncate", "splitOnLast", "contains"]

for in будет показывать только ключи , если они будут добавлены нами, он не собирается показать formatUnicorn
Милад

1
"formatUnicorn", "truncate", "splitOnLast", "содержит" вывод на печать, поскольку переопределение stackoverflow String.prototype.
jasonxia23

6

Есть некоторые уже определенные типы данных, которые позволяют легко их перебирать, например, Array, Map, String Objects

Обычный для in выполняет итерации по итератору и в ответ предоставляет нам ключи в порядке вставки, как показано в примере ниже.

  const numbers = [1,2,3,4,5];
   for(let number in number) {
     console.log(number);
   }

   // result: 0, 1, 2, 3, 4

Теперь, если мы попробуем то же самое с for , то в ответ он предоставит нам значения, а не ключи. например

  const numbers = [1,2,3,4,5];
   for(let numbers of numbers) {
    console.log(number);
  }

  // result: 1, 2, 3, 4, 5

Поэтому, глядя на оба итератора, мы можем легко различить разницу между ними.

Примечание: - Для работы только с Symbol.iterator

Так что, если мы попытаемся перебрать обычный объект, он выдаст нам ошибку, например:

const Room = {
   area: 1000,
   height: 7,
   floor: 2
 }

for(let prop in Room) {
 console.log(prop);
 } 

// Result area, height, floor

for(let prop of Room) {
  console.log(prop);
 } 

Комната не повторяется

Теперь для итерации нам нужно определить ES6 Symbol.iterator, например:

  const Room= {
    area: 1000, height: 7, floor: 2,
   [Symbol.iterator]: function* (){
    yield this.area;
    yield this.height;
    yield this.floors;
  }
}


for(let prop of Room) {
  console.log(prop);
 } 

//Result 1000, 7, 2

В этом разница между For in и For of . Надеюсь, что это может очистить разницу.


5

Еще одно различие между двумя циклами, о котором никто не упоминал ранее:

Уничтожение for...inне рекомендуется. Используйте for...ofвместо этого.

Источник

Поэтому, если мы хотим использовать деструктуризацию в цикле, для получения как индекса, так и значения каждого элемента массива , мы должны использовать for...ofцикл с методом Arrayentries() :

for (const [idx, el] of arr.entries()) {
    console.log( idx + ': ' + el );
}

1
Да @GalMargalit, я внимательно прочитал. Я согласен, что for each...inэто устарело (первый пункт), но я не писал об этом ... Я написал, что "Уничтожение for...inустарело. Используйте for...ofвместо этого". (второй момент): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Согласны ли вы со мной @GalMargalit?
simhumileco

1
Хаха, ты прав, я не читал внимательно! Правда, я в основном думал об одном и том же и думал, что вы имеете в виду другое.
Гал Маргалит

2

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

Используя Object.entriesвы убедитесь, чтобы пройти через все свойства:

var arr = [3, 5, 7];
arr.foo = "hello";

for ( var [key, val] of Object.entries( arr ) ) {
   console.log( val );
}

/* Result:

3
5
7
hello

*/

2

Я вижу много хороших ответов, но я решил поставить свои 5 центов, чтобы иметь хороший пример:

Для в петле

перебирает все перечисляемые реквизиты

let nodes = document.documentElement.childNodes;

for (var key in nodes) {
  console.log( key );
}

Для петли

перебирает все повторяемые значения

let nodes = document.documentElement.childNodes;

for (var node of nodes) {
  console.log( node.toString() );
}


2

Когда я впервые начал изучать цикл for in and of loop , меня тоже смутили мои результаты, но после нескольких исследований и понимания вы можете думать об отдельном цикле следующим образом:

  1. Цикл for ... in возвращает индексы отдельного свойства и не влияет на значение свойства , он зацикливается и возвращает информацию о свойстве, а не о значении . Например

let profile = { name : "Naphtali", age : 24, favCar : "Mustang", favDrink : "Baileys" }

Приведенный выше код просто создает объект с именем profile , мы будем использовать его в обоих наших примерах , поэтому не смущайтесь, когда вы видите объект профиля в примере, просто знайте, что он был создан.

Итак , теперь давайте использовать для ... в цикле ниже

for(let myIndex in profile){
    console.log(`The index of my object property is ${myIndex}`)
}
 // Outputs : 
        The index of my object property is 0
        The index of my object property is 1
        The index of my object property is 2
        The index of my object property is 3

Теперь причиной вывода является то, что у нас есть четыре (4) свойства в нашем объекте профиля, и индексация, как мы все знаем, начинается с 0 ... n , поэтому мы получаем индекс свойств 0,1,2,3, так как мы работая с циклом for..in .

  1. for ... of loop * может возвращать либо свойство , значение, либо оба , давайте посмотрим, как это сделать. В javaScript мы не можем циклически проходить через объекты, как если бы мы работали с массивами, поэтому есть несколько элементов, которые мы можем использовать для доступа к любому из наших выборов из объекта.

    • Object.keys ( object-name-go-here ) >>> Возвращает ключи или свойства объекта.

    • Object.values ( object-name-go-here ) >>> Возвращает значения объекта.

    • Object.entries ( имя-объекта-идет-сюда ) >>> Возвращения оба эти ключи и значения объекта.

Ниже приведены примеры их использования, обратите внимание на Object.entries () :

Step One: Convert the object to get either its key, value, or both.
Step Two: loop through.


// Getting the keys/property

   Step One: let myKeys = ***Object.keys(profile)***
   Step Two: for(let keys of myKeys){
             console.log(`The key of my object property is ${keys}`)
           }

// Getting the values of the property

    Step One: let myValues = ***Object.values(profile)***
    Step Two : for(let values of myValues){
                 console.log(`The value of my object property is ${values}`)
               }

При использовании Object.entries () вы должны вызывать две записи об объекте, то есть ключи и значения. Вы можете позвонить как по одной из записей. Пример ниже.

Step One: Convert the object to entries, using ***Object.entries(object-name)***
Step Two: **Destructure** the ***entries object which carries the keys and values*** 
like so **[keys, values]**, by so doing, you have access to either or both content.


    // Getting the keys/property

       Step One: let myKeysEntry = ***Object.entries(profile)***
       Step Two: for(let [keys, values] of myKeysEntry){
                 console.log(`The key of my object property is ${keys}`)
               }

    // Getting the values of the property

        Step One: let myValuesEntry = ***Object.entries(profile)***
        Step Two : for(let [keys, values] of myValuesEntry){
                     console.log(`The value of my object property is ${values}`)
                   }

    // Getting both keys and values

        Step One: let myBothEntry = ***Object.entries(profile)***
        Step Two : for(let [keys, values] of myBothEntry){
                     console.log(`The keys of my object is ${keys} and its value 
is ${values}`)
                   }

Прокомментируйте непонятные части раздела.


1

for-inцикл

for-inЦикл используется для перемещения по перечисляемым свойствам коллекции в произвольном порядке . Коллекция - это объект типа контейнера, элементы которого могут использовать индекс или ключ.

var myObject = {a: 1, b: 2, c: 3};
var myArray = [1, 2, 3];
var myString = "123";

console.log( myObject[ 'a' ], myArray[ 1 ], myString[ 2 ] );

for-inЦикл извлекает перечисляемые свойства ( ключи ) коллекции сразу и выполняет итерацию по одному за раз. Перечисляемое свойство - это свойство коллекции, которое может появляться в for-inцикле.

По умолчанию все свойства массива и объекта отображаются в for-inцикле. Однако мы можем использовать метод Object.defineProperty для ручной настройки свойств коллекции.

var myObject = {a: 1, b: 2, c: 3};
var myArray = [1, 2, 3];

Object.defineProperty( myObject, 'd', { value: 4, enumerable: false } );
Object.defineProperty( myArray, 3, { value: 4, enumerable: false } );

for( var i in myObject ){ console.log( 'myObject:i =>', i ); }
for( var i in myArray ){ console.log( 'myArray:i  =>', i ); }

В приведенном выше примере, свойство dиз myObjectи индекс 3из myArrayне появляется в for-inцикле , потому что они настроены с enumerable: false.

Есть несколько проблем с for-inпетлями. В случае массивов, for-inцикл также будет учитывать methodsдобавление в массив с использованием myArray.someMethod = fсинтаксиса, однако myArray.lengthостается 4.

for-ofцикл

Это ошибочное мнение, что for-ofцикл повторяется над значениями коллекции. for-ofцикл перебирает Iterableобъект. Итерируемый - это объект, у которого есть метод с именем, Symbol.iteratorпрямо на нем, на одном из его прототипов.

Symbol.iteratorМетод должен возвращать Итератор . Итератор - это объект, у которого есть nextметод. Этот метод при вызове return valueи doneproperties.

Когда мы итерируем итеративный объект, используя for-ofцикл, Symbol.iteratorметод будет вызван после получения объекта итератора . Для каждой итерации for-ofцикла nextметод этого объекта итератора будет вызываться до тех пор, пока doneв результате next()вызова не будет возвращено значение false. Значение, полученное for-ofциклом для каждой итерации, если valueсвойство возвращается при next()вызове.

var myObject = { a: 1, b: 2, c: 3, d: 4 };

// make `myObject` iterable by adding `Symbol.iterator` function directlty on it
myObject[ Symbol.iterator ] = function(){
  console.log( `LOG: called 'Symbol.iterator' method` );
  var _myObject = this; // `this` points to `myObject`
  
  // return an iterator object
  return {
    keys: Object.keys( _myObject ), 
    current: 0,
    next: function() {
      console.log( `LOG: called 'next' method: index ${ this.current }` );
      
      if( this.current === this.keys.length ){
        return { done: true, value: null }; // Here, `value` is ignored by `for-of` loop
      } else {
        return { done: false, value: _myObject[ this.keys[ this.current++ ] ] };
      }
    }
  };
}

// use `for-of` loop on `myObject` iterable
for( let value of myObject ) {
  console.log( 'myObject: value => ', value );
}

for-ofЦикл нового в ES6 и так является Iterable и итерируемый . Тип Arrayконструктора имеет Symbol.iteratorметод на своем прототипе. ObjectКонструктор , к сожалению , не имеет его , но Object.keys(), Object.values()и Object.entries()методы возвращают итератор ( вы можете использовать , console.dir(obj)чтобы проверить методы прототипа ). Преимущество for-ofцикла в том, что любой объект может быть сделан итеративным, даже ваш пользовательский Dogи Animalклассы.

Самый простой способ сделать объект итеративным - это использовать ES6 Generator вместо пользовательской реализации итератора.

В отличие от цикла for-in, for-ofцикл может ожидать завершения асинхронной задачи в каждой итерации. Это достигается с помощью awaitключевого слова после forвыписки документации .

Еще одна замечательная особенность for-ofцикла заключается в том, что он поддерживает Unicode. Согласно спецификации ES6 строки хранятся в кодировке UTF-16. Таким образом, каждый символ может принимать либо 16-bitили 32-bit. Традиционно строки хранились в кодировке UCS-2, которая поддерживает символы, которые могут храниться 16 bitsтолько внутри .

Следовательно, String.lengthвозвращает количество 16-bitблоков в строке. Современные персонажи, такие как Emoji, занимают 32 бита. Следовательно, этот символ возвращает length2. for-inцикл повторяется по 16-bitблокам и возвращает неправильное значение index. Тем не менее, for-ofцикл перебирает отдельные символы на основе спецификаций UTF-16.

var emoji = "😊🤣";

console.log( 'emoji.length', emoji.length );

for( var index in emoji ){ console.log( 'for-in: emoji.character', emoji[index] ); }
for( var character of emoji ){ console.log( 'for-of: emoji.character', character ); }


0

Я нашел следующее объяснение из https://javascript.info/array очень полезным:

Один из самых старых способов циклического перемещения элементов массива - это цикл по индексам:

let arr = ["Apple", "Orange", "Pear"];

for (let i = 0; i < arr.length; i++) { alert( arr[i] ); } But for arrays there is another form of loop, for..of:

let fruits = ["Apple", "Orange", "Plum"];

// iterates over array elements for (let fruit of fruits) { alert( fruit ); } The for..of doesn’t give access to the number of the current element, just its value, but in most cases that’s enough. And it’s shorter.

Технически, поскольку массивы являются объектами, их также можно использовать для ... в:

let arr = ["Apple", "Orange", "Pear"];

for (let key in arr) { alert( arr[key] ); // Apple, Orange, Pear } But that’s actually a bad idea. There are potential problems with it:

Цикл for..in перебирает все свойства, не только числовые.

В браузере и в других средах есть так называемые «подобные массиву» объекты, которые выглядят как массивы. То есть они имеют свойства длины и индексов, но они также могут иметь другие нечисловые свойства и методы, которые нам обычно не нужны. Цикл for..in перечислит их все же. Поэтому, если нам нужно работать с объектами, похожими на массивы, то эти «дополнительные» свойства могут стать проблемой.

Цикл for..in оптимизирован для общих объектов, а не массивов, и, следовательно, в 10-100 раз медленнее. Конечно, это все еще очень быстро. Ускорение может иметь значение только в узких местах. Но все же мы должны осознавать разницу.

Как правило, мы не должны использовать for ..in для массивов.


0

Вот полезная мнемоника для запоминания различий между for...inLoop и for...ofLoop.

"Индекс, объект"

for...in Loop=> перебирает индекс в массиве.

for...of Loop=> перебирает объект объектов.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.