Using Objects in For Of Loops
Asked Answered
B

16

98

Why isn't is possible to use objects in for of loops? Or is this a browser bug? This code doesn't work in Chrome 42, saying undefined is not a function:

test = { first: "one"}

for(var item of test) {
  console.log(item)
}
Biform answered 27/4, 2015 at 0:13 Comment(3)
Is test an array or object?Sallet
@KickButtowski, can't you see? It is definitely an object.Illailladvised
for (let key of Object.keys(test)) { ... }Gesture
B
20

I made objects iterable with this code:

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

Usage:

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

Alternativly:

for(let [ key, value ] of Object.entries({})) { }
Biform answered 19/9, 2015 at 17:34 Comment(18)
No idea why this is the accepted solution. Modifying the prototype unless its a polyfill is always a terrible idea.Rienzi
@Rienzi It is the accepted solution because it works. Please explain what problems this will cause if it is so terrible.Biform
There are all sorts of issues, mainly forward compatibility ones. One example is that Array.prototype.includes which was previously names contains but Moo Tools extended the prototype and the implementation was incompatible, see bugzilla.mozilla.org/show_bug.cgi?id=1075059 Also look up the Prototype library desaster ;)Rienzi
I believe that this will not have forward compatibility issues because if an iterator was added to objects this would overwrite it and if an iterator was added to an object subtype it would use the subtype iterator.Biform
Hey guys, modifying the prototype is a bad idea!!! Let's shame the OP for actually giving an answer to the question!Chinch
Downvoted because of the call to Object.keys. One of the nice things about generators is that they can be chained together lazily. The call to Object.keys ruins the laziness by eagerly creating a new array of the keys. A better solution would be to use for in with a hasOwnProperty check or propertyIsEnumerable check then yieldingBascom
@RicoKahler Would it really have a performance impact? And in my testing it seems like for in loops also work with the keys at the time the loop is entered.Biform
@DanielHerr hmm... if for in also eagerly grabs the keys then there's no difference. it's a small thing though, the performance difference is probably negligible.Bascom
@RicoKahler But the native array and map iterators do include keys added later, so I should probably solve that for consistency.Biform
I did a performance test and it seems like for in is faster. Can you confirm on your browser?Bascom
Oh oops, I never published that test. View it here. jsperf.com/object-keys-vs-for-in-for-object-generators/1Bascom
dangerous, scurryAnticholinergic
@Anticholinergic What?Biform
@DanielHerr Overriding the Object prototype... Very cool & smart solution, but the bigger the project, the bigger the risk that comes in to play.Anticholinergic
@Anticholinergic What exactly is the risk? Can you give me a possible example?Biform
@DanielHerr: It's deemed bad practice in enterprise environments to override native prototypes. Without googling a douglas crawford quote, I'd say the main danger of doing this in general is when ECMA adds/removes props to the prototype which you have now overwritten; could cause a clash, or unexpected behavior. Maintainability-wise, having a larger team, you'll have one person using 'for of' loops on objects and the rest using 'for in', and those whom aren't aware of the shim will auto-think 'they are using for of, this can't be an object', & end up confused or wasting time finding the causeAnticholinergic
@DanielHerr We don't modify prototypes for very good reason. Because this breaks encapsulation and provides global changes in code behaviour. Sometimes it may be a matter of principle, but in this case the pitfall is obvious - if (when) objects will become iterable, reassigned Object.prototype[Symbol.iterator] can break any other spec-compiant piece of code. If you need to customize built-in class behaviour, subclass it, it's always as simple as that. E.g. same thing but done right.Oria
I think OP here is just to show...it's doable (yet not recommended)Humpbacked
D
82

The for..of loop only supports iterable objects like arrays, not objects.

To iterate over the values of an object, use:

for (var key in test) {
    var item = test[key];
}
Debility answered 27/4, 2015 at 0:17 Comment(9)
Neither ES5 nor ES6 define Object.values.Isometrics
Yup, that's what I already use. But what makes an object "iterable"?Biform
@DanielHerr Having an .iterable member function, which is where the error comes from when you try to use it on an object (which doesn't have it). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Debility
I mean, why don't objects have that? What would be the problem with natively adding it?Biform
@DanielHerr I don't have the answer to that, you'll have to ask the people who design the language.Debility
@DanielHerr If the Object "base class" were iterable, so would any Function/Date/etc "subclass" amongst other complications. See esdiscuss.org/topic/es6-iteration-over-object-values#content-5 for a more thorough/accurate discussion of your question though.Furbish
With this for..in solution, don't you still technically have to do a check for if (test.hasOwnProperty(key)){ ... }? Or is that not needed?Hilaryhilbert
@Hilaryhilbert If you're creating a basic object with {}, or getting it from json using JSON.Parse, hasOwnProperty is globally useless. But…: https://mcmap.net/q/218637/-should-hasownproperty-still-be-used-with-for-in-statementsReamonn
@DanielHerr The difference between iterable (e.g. arrays) and non-iterable (native object) collections is that the iterable ones have a solid definition on the order by which their elements should be iterated. Objects are just lookups, and the spec states that you should never rely on the order of keys or values (e.g. when using a for-in loop), hence why they are not considered "iterable".Madlynmadman
W
64

You can use this syntax:

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

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

However, Object.entries has poor support right now does not work in IE or iOS Safari. You'll probably might need a polyfill. See https://caniuse.com/mdn-javascript_builtins_object_entries for the latest scoop.

See also Object.keys to iterate just the keys, or Object.values for just the values.

Waikiki answered 14/4, 2016 at 6:57 Comment(0)
A
36

If you are storing data in a key-value store, please use Map which is explicitly designed for this purpose.

If you have to use an object though, ES2017 (ES8) allows you to use Object.values:

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

If that isn't supported yet, use a polyfill: Alternative version for Object.values()

And finally if you're supporting an older environment that don't support this syntax, you'll have to resort to using forEach and Object.keys:

var obj = { a: 'foo', z: 'bar', m: 'baz' };
Object.keys(obj).forEach(function (prop) {
    var value = obj[prop];
    console.log(value);
});
Attenweiler answered 27/4, 2015 at 0:35 Comment(5)
couldn't Object prototype be extended to support this?Cedar
@SonicSoul: technically yes, but it's generally not advised to extend the Object prototype as (pretty much) everything inherits from it.Attenweiler
Object.entries can by polyfilled without touching the prototype.Waikiki
Why use maps instead of objects?Biform
Is there any advantage to using these complex examples over a simple for-in?Triumph
B
20

I made objects iterable with this code:

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

Usage:

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

Alternativly:

for(let [ key, value ] of Object.entries({})) { }
Biform answered 19/9, 2015 at 17:34 Comment(18)
No idea why this is the accepted solution. Modifying the prototype unless its a polyfill is always a terrible idea.Rienzi
@Rienzi It is the accepted solution because it works. Please explain what problems this will cause if it is so terrible.Biform
There are all sorts of issues, mainly forward compatibility ones. One example is that Array.prototype.includes which was previously names contains but Moo Tools extended the prototype and the implementation was incompatible, see bugzilla.mozilla.org/show_bug.cgi?id=1075059 Also look up the Prototype library desaster ;)Rienzi
I believe that this will not have forward compatibility issues because if an iterator was added to objects this would overwrite it and if an iterator was added to an object subtype it would use the subtype iterator.Biform
Hey guys, modifying the prototype is a bad idea!!! Let's shame the OP for actually giving an answer to the question!Chinch
Downvoted because of the call to Object.keys. One of the nice things about generators is that they can be chained together lazily. The call to Object.keys ruins the laziness by eagerly creating a new array of the keys. A better solution would be to use for in with a hasOwnProperty check or propertyIsEnumerable check then yieldingBascom
@RicoKahler Would it really have a performance impact? And in my testing it seems like for in loops also work with the keys at the time the loop is entered.Biform
@DanielHerr hmm... if for in also eagerly grabs the keys then there's no difference. it's a small thing though, the performance difference is probably negligible.Bascom
@RicoKahler But the native array and map iterators do include keys added later, so I should probably solve that for consistency.Biform
I did a performance test and it seems like for in is faster. Can you confirm on your browser?Bascom
Oh oops, I never published that test. View it here. jsperf.com/object-keys-vs-for-in-for-object-generators/1Bascom
dangerous, scurryAnticholinergic
@Anticholinergic What?Biform
@DanielHerr Overriding the Object prototype... Very cool & smart solution, but the bigger the project, the bigger the risk that comes in to play.Anticholinergic
@Anticholinergic What exactly is the risk? Can you give me a possible example?Biform
@DanielHerr: It's deemed bad practice in enterprise environments to override native prototypes. Without googling a douglas crawford quote, I'd say the main danger of doing this in general is when ECMA adds/removes props to the prototype which you have now overwritten; could cause a clash, or unexpected behavior. Maintainability-wise, having a larger team, you'll have one person using 'for of' loops on objects and the rest using 'for in', and those whom aren't aware of the shim will auto-think 'they are using for of, this can't be an object', & end up confused or wasting time finding the causeAnticholinergic
@DanielHerr We don't modify prototypes for very good reason. Because this breaks encapsulation and provides global changes in code behaviour. Sometimes it may be a matter of principle, but in this case the pitfall is obvious - if (when) objects will become iterable, reassigned Object.prototype[Symbol.iterator] can break any other spec-compiant piece of code. If you need to customize built-in class behaviour, subclass it, it's always as simple as that. E.g. same thing but done right.Oria
I think OP here is just to show...it's doable (yet not recommended)Humpbacked
L
18

Iterator, Iterable and for..of loop in ECMAScript 2015/ ES6

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

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

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

But if we do

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

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

We get error because for..of loop works only on Iterables, that is, the object which has an @@iterator that adheres to Iterator protocol, meaning it must have an object with a next method. The next method takes no arguments and it should return an object with these two properties.

done: signals that the sequence has ended when true, and false means there may be more values value: this is the current item in the sequence

So, to make an object Iterable that is to make it work with for..of we can:

1 .Make an object an Iterable by assigning to it’s mystical @@iterator property through the Symbol.iterator property.Here is how:

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.Use Object.entries, which returns an 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.Use Object.keys, here is how:

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

// a
// b
// c

Hope this helps!!!!!!

Linetta answered 7/5, 2017 at 14:5 Comment(0)
V
12

Because object literal does not have the Symbol.iterator property. To be specific, you can only iterate over String, Array, Map, Set, arguments, NodeList(not widely support) and Generator with for...of loop.

To deal with Object Literal iteration, you have two options.

for...in

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

Object.keys + forEach

Object.keys(obj).forEach(function(key){
    console.log(obj[key]);
});
Virgilvirgilia answered 29/4, 2015 at 3:43 Comment(0)
J
3

The answer is No. It's not possible to use For..Of with Object literals.

I agree with Overv that For..Of is only for iterables. I had exactly the same question because I use Objects to iterate over keys and values with for..in. But I just realized that that's what ES6 MAPS and SETS are for.

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

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

Hence it achieves the goal of not having to use for..In (validating with hasOwnProperty) and not having to use Object.keys().

Additionally, your keys aren't limited to strings. You can use numbers, objects, or other literals.

Josie answered 27/7, 2015 at 4:21 Comment(0)
S
2

Object literals don't have built-in iterators, which are required to work with for...of loops. However, if you don't want to go thru the trouble of adding your own [Symbol.iterator] to your object, you can simply use the Object.keys() method. This method returns an Array object, which already has a built-in iterator, so you can use it with a for...of loop like this:

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
Schmooze answered 25/9, 2016 at 15:43 Comment(1)
Using keys every time is more trouble than adding an iterator once. Also, Object.keys() is ES5.Biform
S
1

It is possible to define an iterator over any giving object, this way you can put different logic for each object

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

Then just directly iterate x

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

It is possible to do the same thing on the Object.prototype object And have a general iterator for all objects

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

then iterate your object like this

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

Or this way

var it = t[Symbol.iterator](), p;
while(p = it.next().value){
    console.log(t[p])
}
Steppe answered 29/12, 2016 at 11:19 Comment(0)
G
1

I just did the following to easily console out my stuff.

for (let key in obj) {
  if(obj.hasOwnProperty(key){
    console.log(`${key}: ${obj[key]}`);
  }
}
Goosestep answered 23/3, 2018 at 19:46 Comment(0)
B
1

How about using Object.keys to get an array of keys? And then forEach on the Array?

obj = { a: 1, b:2}
Object.keys(obj).forEach( key => console.log(`${key} => ${obj[key]}`))
Brazilin answered 22/7, 2018 at 2:33 Comment(0)
M
1

Using Array Destruction you can iterate it as follows using 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"
});
Mayemayeda answered 9/12, 2018 at 11:57 Comment(0)
D
1

One-Line answer based on Mozilla doc Generator - Symbol iterator - Add this to your object to make it iterable:

*[Symbol.iterator]() { yield* Object.values(this) }

example 1 - getting values:

const person = {
  name: "SeyyedKhandon",
  age: 31,
  *[Symbol.iterator]() { yield* Object.values(this) }
}

// Now you can use "for of"
for (let value of person) {
    console.log(value);    
}
// and also you "spread it"
console.log([...person])

example 2 - getting key-values using *[Symbol.iterator]() { yield* Object.entries(this) }:

const person = {
  name: "SeyyedKhandon",
  age: 31,
  *[Symbol.iterator]() { yield* Object.entries(this) }
}

// for of
for (let item of person) {
    console.log("key:",item[0]," -> ", "value:",item[1]); 
}
// spread it
console.log([...person])

Also you can add it later:

person[Symbol.iterator]= function*() { yield* Object.entries(person) }

const person = {
  name: "SeyyedKhandon",
  age: 31,
}
person[Symbol.iterator]= function*() { yield* Object.entries(person) }

// for of
for (let item of person) {
    console.log("key:",item[0]," -> ", "value:",item[1]); 
}
// spread it
console.log([...person])
Dynameter answered 5/12, 2022 at 14:18 Comment(0)
R
0

What about using

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);
}
Rienzi answered 11/8, 2016 at 21:44 Comment(0)
K
0

in ES6 you could go with generator:

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}

Here is the jsfiddle. In the output you will get an object with the "value" and "done" keys. "Value" contains everything you want it to have and "done" is current state of the iteration in bool.

Khedive answered 1/3, 2017 at 12:18 Comment(0)
S
0

This is from 2015, we are in 2022 and I think we can say that a good way to solve this is adding an iterator.

For example if we had a snippet like this:

const obj = { a: 1, b: 2, c: 3 }
const [first] = obj

It will give us an error that object is not iterable

However, it can be iterated using for ... in

const obj = { a: 1, b: 2, c: 3 }
for (const key in obj) {
  console.log('key: ', key)
}

Teo, how we can make our obj instance iterable?

Well it is easy just add the iterable protocol like this:

const obj = { a: 'one', b: 'two', c: 'three' }
obj[Symbol.iterator] = () => {
    const entries = Object.entries(obj)
    return {
        next() {
          return {
            done: entries.length === 0,
            value: entries.shift(),
          }
        },
    }
}

const [first] = obj
const [k, v] = first
console.log(`first: [k: ${k}, v:${v}]`)

// Now we can use for of
for (const [k, v] of obj) {
  console.log(`[k:${k}, v:${v}]`)
}
This is a very basic implementation you can replace Object.entries(obj) with Object.keys(obj) or Object.values(obj) according to the needs of the project.
Semimonthly answered 28/10, 2022 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.