Reputation: 711
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
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
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