9

I am coming across terms Iterable and Enumerable while studying For/in and For/of loops. Objects are supposed be enumerable and we have to use For/in loop to loop over the properties of the object and For/of to loop over the arrays and strings. I am unable to wrap my head around these two terms. What is the difference between these two?

Mahwash
  • 91
  • 1
  • 4
  • Does this answer your question? [Distinction between iterator and enumerator](https://stackoverflow.com/questions/716238/distinction-between-iterator-and-enumerator) – derpirscher Aug 04 '21 at 08:37
  • 2
    An object property can be enumerable, that means, it is recognized by the ``for ... in`` loop. An object (not a specific property) can be iterable, so you can loop over it via ``for ... of``. – Tracer69 Aug 04 '21 at 08:38
  • Does this mean that enumerable is also iterable? – Mahwash Aug 04 '21 at 08:41
  • @Mahwash No, these are two separate concepts. You can have a non-enumerable property that is iterable. Or an enumerable property that is not iterable. – VLAZ Aug 04 '21 at 08:49
  • 3
    @derpirscher I don't think this applies here. Enumerables in C# are not enumerable *properties*. `IEnumerable` is in interface for *things* you can iterate over, like collections or some generators. In JS "enumerable" is a flag on *object properties*. – VLAZ Aug 04 '21 at 08:51
  • 2
    Did you have a look at [What is the difference between ( for… in ) and ( for… of ) statements?](https://stackoverflow.com/q/29285897/1048572)? I doesn't go in depth though – Bergi Aug 04 '21 at 08:55

2 Answers2

11

There are a few things that stand out one from another.

A bit about Iterable:

  • Iterable objects are a generalization of arrays. That's a concept that allows us to make any object useable in a for..of the loop;
  • The iterable is an interface that specifies that an object can be accessible if it implements a method who is key is [symbol.iterator] link.

A bit about Enumerable:

  • It simply means that the property will show up if you iterate over the object using for..in loop or Object.keys;
  • An enumerable property in JavaScript means that a property can be viewed if it is iterated using the for…in loop or Object.keys() method. All the properties which are created by simple assignment or property initializer are enumerable by default.
  1. Enumerable [for in] looking at the properties that are inside of the object, not the values [only where enumerable: true - by default for all props];
  2. Iterable [for of] looking at the values;

A bit more in-depth:

Iterator is another object that is attached to the array, and it tells other function how to access all the different values inside of it. there are array, string, NodeList, Sets, Maps they have built-in iterators, but the object does not have it.

The object is not iterable by default, but you can implement it.

So you can use

  • for .. of for [array, Map, Set, String] to iterate over values;
  • for .. in for an array to iterate over a key;
  • for .. in for objects to enumerate its (object's) properties;
  • looping over NodeList.

Please, take a look at the example either here or using a provided link for a sandbox. Sandbox link for the same example.

let arr = ['value1', 'value2', 'value3'];

let obj = {
  propName1: 'propValue1',
  propName2: 'propValue2',
  propName3: 'propValue3'
};

console.log('=====================WORKING WITH ARRAYS===================');
console.log('For Of ')
for (const value of arr) {
  console.log('value: ', value);
}

console.log('For In');
for (const key in arr) {
  console.log('key: ', key, ' value: ', arr[key]);
}

console.log('=====================WORKING WITH OBJECTS===================');
console.log('For In:');
for (const prop in obj) {
  console.log('prop: ', prop, 'value: ', obj[prop]);
}

Object.defineProperty(obj, "definedPropEnFalse", {
  value: 'value of definedPropEnFalse',
  enumerable: false,
});

Object.defineProperty(obj, "definedPropEnTrue", {
  value: 'value of definedPropEnTrue',
  enumerable: true,
});

console.log('For In for Objects with enumerables:');
for (const prop in obj) {
  console.log('prop: ', prop, 'value: ', obj[prop]);
}

console.log('For In for Objects with Object.keys and forEach:');
Object.keys(obj).forEach(e => console.log(`key=${e}  value=${obj[e]}`));


console.log('=====================WORKING WITH STRINGS===================');
let str = "Go Over A String"
console.log('Using For Of for String:');
for (const char of str) {
  console.log(char);
}


console.log('=====================WORKING WITH Sets===================');
console.log("Looping over a Set");
let testSet = new Set();
testSet.add('Hello');
testSet.add('Hope');
testSet.add('You are getting it xD');

for (const setItem of testSet) {
  console.log(setItem);
}


console.log('=====================WORKING WITH Maps===================');
console.log('Iterate over Map using For of')
var myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");

for (const [key, value] of myMap.entries()) {
  console.log(key, value);
}

You may ask how should I remember it? - Easy!

A mnemonic:

  • 'o'f -> not 'o'bjects;
  • 'i'n -> not 'i'terables.

Another mnemonic:

  • for..in..keys === foreign keys === use for...in for keys;
  • for...of for values.

in gives you index.

Taken from this post's comment


If an object isn’t technically an array but represents a collection (list, set) of something, then for..of is a great syntax to loop over it.

Utmost Creator
  • 814
  • 1
  • 9
  • 21
  • 1
    "*a property can be viewed if it is iterated using the for…in loop*" - to avoid confusion, I recommend saying that a `for … in` loop **enumerates** the properties of the object :-) – Bergi Aug 04 '21 at 13:42
  • 3
    "*So you can use for .. in for an array to iterate over a key;*" - no, [please don't!](https://stackoverflow.com/q/500504/1048572) – Bergi Aug 04 '21 at 13:43
  • Yes, thanks, I was trying to make this sentence. But could not xD. Thank you Bergi! I have corrected this one – Utmost Creator Aug 04 '21 at 14:27
5

Iterable applies to values

A value can be iterable or not. It needs to implement the well known symbol @@iterator or @@asyncIterator. The methods implemented must also fulfil the iterable protocol by returning an iterator. With those in place, there are special interactions that can be done with such values by treating them as something that can be iterated over (hence the "iterable" name). Here are some examples:

for...of

The most basic and perhaps most common use for iterables is to iterate over them. The for...of loop will do just that and take items from an iterator until there are none left.

String:

const str = "hello world";

for (const char of str)
  console.log(char);
.as-console-wrapper { max-height: 100% !important; }

Array

const arr = ["a", "b", "c", "d"];

for (const item of arr)
  console.log(item);
.as-console-wrapper { max-height: 100% !important; }

Custom object

const iterable = {
  [Symbol.iterator]() {
    let repeat = 0;
    return {
      next() {
        return {
          value: 42, 
          done: repeat++ >= 3
        };
      }
    }
  }
}

for (const item of iterable)
  console.log(item);
.as-console-wrapper { max-height: 100% !important; }

Spread syntax ...

When spreading values, the iterator is used and you get something for each value that comes from that iterator. For example spreading into an array [...value] will create an array with all values. Spreading into a function call fn(...value) will call the function with each item as an argument.

String

const str = "hello world";

console.log([...str]); //spread into array
console.log(...str);   //spread into function call
.as-console-wrapper { max-height: 100% !important; }

Array

const arr = ["a", "b", "c", "d"];

console.log([...arr]); //spread into array
console.log(...arr);   //spread into function call
.as-console-wrapper { max-height: 100% !important; }

Custom object

const iterable = {
  [Symbol.iterator]() {
    let repeat = 0;
    return {
      next() {
        return {
          value: 42, 
          done: repeat++ >= 3
        };
      }
    }
  }
}

console.log([...iterable]); //spread into array
console.log(...iterable);   //spread into function call
.as-console-wrapper { max-height: 100% !important; }

Array destructuring

The name might be slightly misleading. Array destructuring always uses the iterator of an object. It does not mean it can only be used on arrays.

String

const str = "hello world";

const [first, second] = str;
console.log(first, second);
.as-console-wrapper { max-height: 100% !important; }

Array

const arr = ["a", "b", "c", "d"];

const [first, second] = arr;
console.log(first, second);
.as-console-wrapper { max-height: 100% !important; }

Custom object

const iterable = {
  [Symbol.iterator]() {
    let repeat = 0;
    return {
      next() {
        return {
          value: 42, 
          done: repeat++ >= 3
        };
      }
    }
  }
}

const [first, second] = iterable;
console.log(first, second);
.as-console-wrapper { max-height: 100% !important; }

Enumerable is for object properties

Only object properties can be enumerable. Not any value. This can be configured by using Object.defineProperty() or Object.defineProperties() or Reflect.defineProperty() or Object.create().

Non-enumerable object properties

It is hard to get an exhaustive list but this conveys the idea - non-enumerable properties are excluded from some "bulk" operations on properties.

However, non-enumerable properties are still accessible directly. They are not "hidden" or "private", just do not show up with the most common mechanisms to grab all properties.

const obj = Object.defineProperties({}, {
  "a": { value: 1, enumerable: true},
  "b": { value: 2, enumerable: false},
  "c": { value: 3, enumerable: true},
});

for (const prop in obj)
  console.log("for...in:", prop); //a, c
  
console.log("Object.keys():", Object.keys(obj));     // [ "a", "c" ]
console.log("Object.values():", Object.values(obj)); // [ 1, 3 ]

const clone1 = {...obj};
console.log("clone1:", clone1);               // { "a": 1, "c": 3 }
console.log('"b" in clone1:', "b" in clone1); // false
console.log("clone1.b:", clone1.b);           // undefined

const clone2 = Object.assign({}, obj);
console.log("clone2:", clone2);               // { "a": 1, "c": 3 }
console.log('"b" in clone2:', "b" in clone2); // false
console.log("clone2.b:", clone2.b);           // undefined

//still accessible
console.log('"b" in obj:', "b" in obj); // true
console.log("obj.b:", obj.b);           // 2
.as-console-wrapper { max-height: 100% !important; }

There are also mechanisms that allow seeing the non-enumerable properties: Object.getOwnPropertyNames() and Object.getOwnPropertyDescriptors() for example will be able to show them.

VLAZ
  • 26,331
  • 9
  • 49
  • 67