4

If I do this in node (v10.15.3):

Object.keys({test: 'test'}).forEach(() => console.log('gfg'));

I get:

gfg

If I do:

Array(3).keys()

I get:

Object [Array Iterator] {}

So I clearly have a type that is in some sense iterable.

However,

Array(3).keys().forEach(() => console.log('gfg'));

'Yields':

TypeError: Array(...).keys(...).forEach is not a function

Why does the forEach function not exist on this iterable object when the keys are derived from an array?

Peter David Carter
  • 2,548
  • 8
  • 25
  • 44
  • Note the difference in how you're calling them; `Object.keys(obj)` is calling a static method of `Object`, passing in the object as a parameter. `Array(3).keys()` is creating an array, then calling a `keys()` method on the instance. – Heretic Monkey Aug 08 '19 at 14:54
  • @charlietfl my array values are all undefined. I was under the impression from the documentation I was reading that .keys() doesn't ignore undefined values, which is why I was using it. – Peter David Carter Aug 08 '19 at 14:56
  • 1
    https://stackoverflow.com/questions/9329446/for-each-over-an-array-in-javascript/9329476#9329476 check this answer it has all the details – dota2pro Aug 08 '19 at 15:05

2 Answers2

4

This is because Array.keys is NOT the same as Object.keys.

Array.keys return an iterator (or, really an "Array Iterator"), it doesn't expose any array prototype because it does not return an array, though it can be looped:

for (var elem of Array(3).keys()){
 console.log(elem);
}

// If you really want to use forEach...
[...Array(3).keys()].forEach(k => console.log('spread syntax -> ', k));

// Or using Array.from
Array.from(Array(3).keys()).forEach(k => console.log('Array.from ->', k));

So, what is the difference?

The difference is that Array.keys returns an iterable, while Object.keys returns an array. Because Array.keys returns an iterable, it can't directly use array methods, because it's just an iterable (or, really, something that has a [Symbol.iterator]. Object.keys, instead, returns an array, hence it can use any of its prototypes and, since it's an array, also happens to be iterable because, as mentioned before, Array is a built in type that has a default iteration behavior.

briosheje
  • 7,356
  • 2
  • 32
  • 54
  • Wouldn't it make sense to have the forEach method on the iterator, or is that not how things work? – Peter David Carter Aug 08 '19 at 14:50
  • @PeterDavidCarter `forEach` is a **method**, an iterator is just a different type. Foreach is an Array's prototype method, an iterator is **not** an array. You can conveniently use the spread operator or Array.from if you want to use `forEach` though, since you can build an array out of an iterator (see the updated code above) – briosheje Aug 08 '19 at 14:53
  • So we can call the spread operator on the iterator...?? could you explain why this helps us? – Peter David Carter Aug 08 '19 at 14:53
  • @PeterDavidCarter the spread operator just collects all the values from an `iterator` or, really, anything that has a `[Symbol.iterator]` (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator). You are confusing iterators an arrays: they are **different types**. You can think an iterator as some sort of **interface** that allows something to be able to be **looped**. The spread syntax (...) is a shorthand for collecting all the values of an iterable, hence you can use the spread syntax to build an array out of an interator. – briosheje Aug 08 '19 at 14:55
  • 1
    If you want to, I can try to give you a further explanation, it's just that **arrays are iterables**, but an **iterator is not an array**. Both can be looped in a for loop, but **forEach** is an **array method**. `Array.keys` returns an **iterator**, it does NOT return an array, while `Object.keys` returns an **array**. Both can be looped (because they conveniently are iterable, however `Array.keys` is not an array, hence it cannot use `.forEach` directly. – briosheje Aug 08 '19 at 14:57
1

Convert it back to an array and then iterate over it

([...Array(3).keys()]).forEach(() => console.log('gfg'));

you can see the keys() doesnt return an array

console.log(Array.isArray(Array(3)));
console.log(Array.isArray(Array(3).keys()));
dota2pro
  • 7,220
  • 7
  • 44
  • 79
  • Ah, so the array prototype has the forEach method but an iterator doesn't. This seems odd to me... – Peter David Carter Aug 08 '19 at 14:54
  • @PeterDavidCarter foreach is an array prototype method and will on arrays only not objects https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach – dota2pro Aug 08 '19 at 15:01