Использование объектов в циклах For Of

Почему нельзя использовать объекты в циклах for? Или это глюк браузера? Этот код не работает в Chrome 42, говоря, что undefined не является функцией:

test = { first: "one"}

for(var item of test) {
  console.log(item)
}

person Daniel Herr    schedule 27.04.2015    source источник
comment
Является ли тест массивом или объектом?   -  person Kick Buttowski    schedule 27.04.2015
comment
@KickButtowski, разве ты не видишь? Это определенно объект.   -  person Green    schedule 18.08.2016
comment
for (пусть ключ Object.keys(test)) { ... }   -  person clocksmith    schedule 15.02.2017


Ответы (14)


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

Чтобы перебрать значения объекта, используйте:

for (var key in test) {
    var item = test[key];
}
person Overv    schedule 27.04.2015
comment
Ни ES5, ни ES6 не определяют Object.values. - person Oriol; 27.04.2015
comment
Да, это то, что я уже использую. Но что делает объект итерируемым? - person Daniel Herr; 27.04.2015
comment
@DanielHerr Наличие функции-члена .iterable, откуда возникает ошибка, когда вы пытаетесь использовать ее для объекта (у которого ее нет). developer.mozilla.org/en-US/docs/Web/ JavaScript/Справочник/ - person Overv; 27.04.2015
comment
Я имею в виду, почему у объектов этого нет? В чем проблема добавить его изначально? - person Daniel Herr; 27.04.2015
comment
@DanielHerr У меня нет ответа на этот вопрос, вам придется спросить людей, которые разрабатывают язык. - person Overv; 27.04.2015
comment
@DanielHerr Если бы базовый класс Object был итерируемым, то же самое было бы и с любым подклассом Function/Date/etc среди других сложностей. См. esdiscuss.org/topic/es6-iteration-over-object. -values#content-5 для более тщательного/точного обсуждения вашего вопроса. - person natevw; 07.07.2016
comment
С этим решением for..in разве вам технически не нужно выполнять проверку на if (test.hasOwnProperty(key)){ ... }? Или это не нужно? - person tennisgent; 14.09.2016
comment
@tennisgent Если вы создаете базовый объект с помощью {} или получаете его из json с помощью JSON.Parse, hasOwnProperty глобально бесполезен. Но…: stackoverflow.com/a/11314053/1250044 - person yckart; 07.05.2017
comment
@DanielHerr Разница между итерируемыми (например, массивами) и неитерируемыми (собственными объектами) коллекциями заключается в том, что итерируемые коллекции имеют четкое определение порядка, в котором должны повторяться их элементы. Объекты - это просто поиск, и в спецификации указано, что вы никогда не должны полагаться на порядок ключей или значений (например, при использовании цикла for-in), поэтому они не считаются итерируемыми. - person Ciabaros; 07.12.2019

Вы можете использовать этот синтаксис:

const myObject = {
  first: "one",
  second: "two",
};

for (const [key, value] of Object.entries(myObject)) {
  console.log(key, value);  // first one, second two
}

Однако Object.entries имеет плохую поддержку сейчас не работает в IE или iOS Safari. Вам возможно может понадобиться полифилл. См. https://caniuse.com/mdn-javascript_builtins_object_entries для получения последней информации.

См. также Object.keys для повторения только ключей или Object.values только для значений.

person mpen    schedule 14.04.2016

Если вы храните данные в хранилище "ключ-значение", < strong>пожалуйста, используйте Map, специально предназначенный для этой цели.

Однако, если вам нужно использовать объект, ES2017 (ES8) позволяет вам использовать Object.values:

const foo = { a: 'foo', z: 'bar', m: 'baz' };
for (let value of Object.values(foo)) {
    console.log(value);
}

Если это еще не поддерживается, используйте полифилл: Альтернативная версия для Object.values()

И, наконец, если вы поддерживаете более старую среду, которая не поддерживает этот синтаксис, вам придется прибегнуть к использованию forEach и Object.keys:

var obj = { a: 'foo', z: 'bar', m: 'baz' };
Object.keys(obj).forEach(function (prop) {
    var value = obj[prop];
    console.log(value);
});
person Qantas 94 Heavy    schedule 27.04.2015
comment
нельзя ли расширить прототип объекта для поддержки этого? - person Sonic Soul; 26.06.2016
comment
@SonicSoul: технически да, но обычно не рекомендуется расширять прототип объекта, поскольку (в значительной степени) все наследуется от него. - person Qantas 94 Heavy; 29.06.2016
comment
Object.entries можно полифилить, не касаясь прототипа. - person mpen; 11.08.2016
comment
Зачем использовать карты вместо объектов? - person Daniel Herr; 30.10.2016
comment
Есть ли какое-то преимущество в использовании этих сложных примеров перед простым for-in? - person 1252748; 17.08.2017

Iterator, Iterable и цикл for..of в ECMAScript 2015/ES6

let tempArray = [1,2,3,4,5];

for(element of tempArray) {
  console.log(element);
}

// 1
// 2
// 3
// 4
// 5

Но если мы сделаем

let tempObj = {a:1, b:2, c:3};

for(element of tempObj) {
   console.log(element);
}
// error

Мы получаем ошибку, потому что цикл for..of работает только с Iterables, то есть с объектом, который имеет @@iterator, который придерживается < strong>Протокол итератора, что означает, что он должен иметь объект с методом next. Следующий метод не принимает аргументов и должен возвращать объект с этими двумя свойствами.

done: сигнализирует, что последовательность завершена, когда true, а false означает, что могут быть другие значения value: это текущий элемент в последовательности

Итак, чтобы сделать объект Итерируемым, чтобы он работал с for..of, мы можем:

1. Сделайте объект Итерируемым, назначив его мистическое свойство @@iterator через свойство Symbol.iterator. Вот как это сделать:

let tempObj = {a:1, b:2, c:3};

tempObj[Symbol.iterator]= () => ({
next: function next () {
return {
    done: Object.keys(this).length === 0,
    value: Object.keys(this).shift()
     }
    }
  })

for(key in tempObj){
 console.log(key)
}
// a
// b
// c

2. Используйте Object.entries, который возвращает Iterable:

let tempObj = {a:1, b:2, c:3};

for(let [key, value] of Object.entries(tempObj)) {
    console.log(key, value);
}
// a 1
// b 2
// c 3

3. Используйте Object.keys следующим образом:

let tempObj = {a:1, b:2, c:3};
for (let key of Object.keys(tempObj)) {
    console.log(key);
}

// a
// b
// c

Надеюсь это поможет!!!!!!

person Manishz90    schedule 07.05.2017

Я сделал объекты итерируемыми с помощью этого кода:

Object.prototype[Symbol.iterator] = function*() {
 for(let key of Object.keys(this)) {
  yield([ key, this[key] ])
} }

Использование:

for(let [ key, value ] of {}) { }

Альтернативно:

for(let [ key, value ] of Object.entries({})) { }
person Daniel Herr    schedule 19.09.2015
comment
Не знаю, почему это принятое решение. Изменение прототипа, если это не полифилл, — всегда ужасная идея. - person user1703761; 11.08.2016
comment
@user1703761 user1703761 Это принятое решение, потому что оно работает. Пожалуйста, объясните, какие проблемы это вызовет, если это так ужасно. - person Daniel Herr; 11.08.2016
comment
Есть всевозможные проблемы, в основном с прямой совместимостью. Одним из примеров является то, что Array.prototype.includes, который ранее имел имена, содержит, но Moo Tools расширил прототип, и реализация оказалась несовместимой, см. bugzilla.mozilla.org/show_bug.cgi?id=1075059 Также посмотрите desaster библиотеки Prototype ;) - person user1703761; 12.08.2016
comment
Я считаю, что это не будет иметь проблем с прямой совместимостью, потому что, если итератор был добавлен к объектам, он перезапишет его, а если итератор будет добавлен к подтипу объекта, он будет использовать итератор подтипа. - person Daniel Herr; 12.08.2016
comment
Как красиво.. (2x) - person Thales P; 13.09.2016
comment
Эй, ребята, модифицировать прототип - плохая идея!!! Давайте пристыдим ОП за то, что он действительно дал ответ на вопрос! - person NiCk Newman; 17.11.2016
comment
Проголосовали против из-за вызова Object.keys. Одна из приятных особенностей генераторов заключается в том, что их можно лениво соединять в цепочку. Вызов Object.keys разрушает лень, жадно создавая новый массив ключей. Лучшим решением было бы использовать for in с hasOwnProperty чеком или propertyIsEnumerable чеком, затем уступает - person Rico Kahler; 25.05.2017
comment
@RicoKahler Действительно ли это повлияет на производительность? И в моем тестировании кажется, что циклы for in также работают с ключами во время входа в цикл. - person Daniel Herr; 25.05.2017
comment
@DanielHerr хм ... если for in тоже жадно хватает ключи, то нет никакой разницы. это мелочь, разница в производительности, вероятно, незначительна. - person Rico Kahler; 25.05.2017
comment
@RicoKahler Но собственные итераторы массива и карты включают ключи, добавленные позже, поэтому мне, вероятно, следует решить это для согласованности. - person Daniel Herr; 25.05.2017
comment
Я провел тест производительности, и мне кажется, что for in быстрее. Вы можете подтвердить в своем браузере? - person Rico Kahler; 25.05.2017
comment
Ой, я так и не опубликовал этот тест. Посмотреть здесь. jsperf.com/object-keys-vs-for- in-for-object-generators/1 - person Rico Kahler; 25.05.2017
comment
опасно, суетиться - person webdevinci; 26.05.2017
comment
@webdevinci Что? - person Daniel Herr; 26.05.2017
comment
@DanielHerr Переопределение прототипа объекта ... Очень крутое и умное решение, но чем больше проект, тем больше риск, который возникает в игре. - person webdevinci; 28.05.2017
comment
@webdevinci В чем именно заключается риск? Можете ли вы привести возможный пример? - person Daniel Herr; 28.05.2017
comment
@DanielHerr: в корпоративных средах считается плохой практикой переопределять собственные прототипы. Не гугля цитату Дугласа Кроуфорда, я бы сказал, что основная опасность этого в целом заключается в том, что ECMA добавляет/удаляет реквизиты к прототипу, который вы сейчас перезаписали; может вызвать конфликт или неожиданное поведение. С точки зрения ремонтопригодности, имея большую команду, у вас будет один человек, использующий циклы for of для объектов, а остальные используют for in, а те, кто не знает о прокладке, будут автоматически думать, что они используют из-за того, что это не может быть объектом », и в конечном итоге запутаться или тратить время на поиск причины - person webdevinci; 29.05.2017
comment
@DanielHerr Мы не изменяем прототипы по очень веской причине. Потому что это нарушает инкапсуляцию и обеспечивает глобальные изменения в поведении кода. Иногда это может быть делом принципа, но в данном случае ловушка очевидна — если (когда) объекты станут итерируемыми, переназначение Object.prototype[Symbol.iterator] может сломать любой другой соответствующий спецификации фрагмент кода. Если вам нужно настроить встроенное поведение класса, создайте его подкласс, это всегда так просто. Например. то же самое, но сделано правильно. - person Estus Flask; 01.09.2017
comment
Я думаю, что ОП здесь просто для того, чтобы показать ... это выполнимо (но не рекомендуется) - person ngakak; 10.08.2018

Поскольку литерал объекта не имеет свойства Symbol.iterator . Чтобы быть точным, вы можете перебирать только String, Массив, Карта, Set, аргументы, NodeList (не широко поддерживается) и Генератор с for...of.

Чтобы иметь дело с итерацией Object Literal, у вас есть два варианта.

для ... в

for(let key in obj){
    console.log(obj[key]); 
}

Object.keys + forEach

Object.keys(obj).forEach(function(key){
    console.log(obj[key]);
});
person Lewis    schedule 29.04.2015

Ответ: Нет. Невозможно использовать For..Of с литералами Object.

Я согласен с Overv, что For..Of предназначен только для итераций. У меня был точно такой же вопрос, потому что я использую объекты для перебора ключей и значений с помощью for..in. Но я только что понял, что это то, что ES6 MAPS и SETS для.

let test = new Map();
test.set('first', "one");
test.set('second', "two");

for(var item of test) {
  console.log(item); // "one" "two"
}

Следовательно, достигается цель: не использовать for..In (проверка с помощью hasOwnProperty) и не использовать Object.keys().

Кроме того, ваши ключи не ограничены строками. Вы можете использовать числа, объекты или другие литералы.

person cuadraman    schedule 27.07.2015

Литералы объектов не имеют встроенных итераторов, которые необходимы для работы с циклами for...of. Однако, если вы не хотите заморачиваться добавлением собственного [Symbol.iterator] к вашему объекту, вы можете просто использовать метод Object.keys(). Этот метод возвращает объект Array, у которого уже есть встроенный итератор, поэтому вы можете использовать его с циклом for...of следующим образом:

const myObject = {
    country: "Canada",
    province: "Quebec",
    city: "Montreal"
}

for (let i of Object.keys(myObject)) {
    console.log("Key:", i, "| Value:", myObject[i]);
}

//Key: country | Value: Canada
//Key: province | Value: Quebec
//Key: city | Value: Montreal
person Chunky Chunk    schedule 25.09.2016
comment
Использование ключей каждый раз доставляет больше хлопот, чем однократное добавление итератора. Кроме того, Object.keys() — это ES5. - person Daniel Herr; 25.09.2016

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

var x = { a: 1, b: 2, c: 3 }
x[Symbol.iterator] = function* (){
    yield 1;
    yield 'foo';
    yield 'last'
}

Затем просто повторите x

for (let i in x){
    console.log(i);
}
//1
//foo
//last

То же самое можно сделать с объектом Object.prototype И иметь общий итератор для всех объектов

Object.prototype[Symbol.iterator] = function*() {
    for(let key of Object.keys(this)) {
         yield key 
    } 
 }

затем повторите свой объект следующим образом

var t = {a :'foo', b : 'bar'}
for(let i of t){
    console.log(t[i]);
}

Или так

var it = t[Symbol.iterator](), p;
while(p = it.next().value){
    console.log(t[p])
}
person Yaki Klein    schedule 29.12.2016

Я просто сделал следующее, чтобы легко утешить свои вещи.

for (let key in obj) {
  if(obj.hasOwnProperty(key){
    console.log(`${key}: ${obj[key]}`);
  }
}
person DaFrenzy    schedule 23.03.2018

Как насчет использования Object.keys получить массив ключей? А затем для каждого в массиве?

obj = { a: 1, b:2}
Object.keys(obj).forEach( key => console.log(`${key} => ${obj[key]}`))
person justingordon    schedule 22.07.2018

Как насчет использования

function* entries(obj) {
    for (let key of Object.keys(obj)) {
        yield [key, obj[key]];
    }
}

for ([key, value] of entries({a: "1", b: "2"})) {
    console.log(key + " " + value);
}
person user1703761    schedule 11.08.2016

в ES6 вы можете использовать генератор:

var obj = {1: 'a', 2: 'b'};

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

let generator = entries(obj);

let step1 = generator.next();
let step2 = generator.next();
let step3 = generator.next();

console.log(JSON.stringify(step1)); // {"value":["1","a"],"done":false}
console.log(JSON.stringify(step2)); // {"value":["2","b"],"done":false}
console.log(JSON.stringify(step3)); // {"done":true}

Вот jsfiddle. В результате вы получите объект с ключами "value" и "done". "Value" содержит все, что вы хотите, а "done" – текущее состояние итерации в логическом значении.

person Serge Nikolaev    schedule 01.03.2017

Используя уничтожение массива, вы можете повторить его следующим образом, используя forEach

const obj = { a: 5, b: 7, c: 9 };

Object.entries(obj).forEach(([key, value]) => {
  console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});
person Nur Rony    schedule 09.12.2018