Dave C
Dave C

Reputation: 711

Calling forEach on an object

Crockford's The Good Parts book tells me that JavaScript arrays are really objects.

Given that, why doesn't this work? (And am I weird for expecting it to?)

arr={0:'a', 1:'b', 2:'c', 3:'d'}

Array.prototype.forEach.call(arr, function(el){console.log(el)})

My thinking: If arrays are objects then presumably methods like forEach loop through an array's properties in ascending key order. (And through call, we can set the this that the method operates on.) I'm assuming that that's why [].forEach.call(NodeList) works.

Upvotes: 3

Views: 2535

Answers (2)

Oriol
Oriol

Reputation: 288080

Your reasoning is wrong. Arrays are objects, but that does not imply array methods are supposed to work for arbitrary objects. They do, though, because the spec says so:

The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

Of course, in order to work as expected, the object must be enough array-like. That means it must have a length property whose value is an integer larger than the array indices you want to iterate.

Array.prototype.forEach.call(
  {0:'a', 1:'b', 2:'c', 3:'d', length: 4},
  function(el){ console.log(el); }
);

If you don't know the appropriate length beforehand, you may try

console.log(Object.assign([], {0:'a', 1:'b', 2:'c', 3:'d'}).length); // 4

Be aware that will call getters and skip non-enumerable properties. And once the array is built, you can call forEach directly on it instead of on the original object.

Alernatively, if your object is ordinary, getOwnPropertyNames is required to return the keys with the integer indices sorted first. Assuming all keys are integer indices,

var props = Object.getOwnPropertyNames({0:'a', 1:'b', 2:'c', 3:'d'});
console.log(props.length ? +props[props.length-1]+1 : 0); // 4

If the array integer indices are not sparse, props.length could also be used.

Upvotes: 1

user1106925
user1106925

Reputation:

The problem is that your object does not have a length telling .forEach() how far to iterate.

This is important because JS arrays can be sparse, so it can't simply check for the existence of the property to determine if it should quit, and it would just keep incrementing, assuming that the next index may have a value.

So without the length properly set, the iteration fails on (or before, I don't remember) the first comparison.

In your example, to correctly represent the object as an array-like object, it could look like this:

var arr={0:'a', 1:'b', 2:'c', 3:'d', length: 4}

Now .forEach() will successfully iterate indices 0-3.

Upvotes: 3

Related Questions