40

I've found that I can't use a for each loop on an empty array in javascript. Can anyone explain to me why this is?

I've initialized an array in javascript like so:

var arr = new Array(10);

when I use a for each loop on the array nothing happens:

arr.forEach(function(i) {
    i = 0;
});

the result is still an array of undefined values:

arr = [ , , , , , , , , , ];

I think that maybe since each item in the array is undefined, it doesn't even execute the forEach. I would think that it would still iterate through the undefined items. Can anyone explain why this is occurring? This question is not asking how to most efficiently fill an array with zeros, it's asking details on the interaction of a for each loop and an empty array.

7
  • There is a popular dup somewhere... Array.apply(0, Array(10)).map(function(){return 0}) Commented Dec 11, 2014 at 21:53
  • That's a strange result, I would have thought you had an empty array with a length of 10 ? Commented Dec 11, 2014 at 21:55
  • 2
    @adeneo: It'd be an array of 10 undefineds. Commented Dec 11, 2014 at 21:56
  • @RocketHazmat - of course it would, I'm confused! Commented Dec 11, 2014 at 21:58
  • 1
    @RocketHazmat Alerting the array would give a list of commas (in FF at least, though I think in other browsers as well). Interestingly console.log gave Array [ <10 empty slots> ]. Commented Dec 11, 2014 at 22:02

6 Answers 6

31

You can use a forEach like you intended if you modify the array initialization to be:

var arr = Array.apply(null, Array(10))

And then you can do a foreach like:

arr.forEach(function(el, index) {
    arr[index] = 0;
});

The result is:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Sign up to request clarification or add additional context in comments.

2 Comments

Does this effect performance
The algorithmic perf is not affected. However, when talking about performance the smartest option is to micro-benchmark yourself.
26

You're half-way right!

I think that maybe since each item in the array is undefined, it doesn't even execute the forEach.

Array.prototype.forEach does not visit indices which have been deleted or elided; this is a process called ellision. So, it executes, but skips over every element.

From MDN: Screenshot from MDN regarding forEach

2 Comments

That is not entirely correct. If you use var arr = Array.apply(null, Array(10)) like in my answer below it will loop correctly through the undefined values with the forEach as the OP intented.
To elaborate on @TheZenCoder's comment, perhaps you should amend your text to say "Array.prototype.forEach does not visit indices which have been deleted or elided", as stated in the MDN reference. It does visit elements which are undefined.
7

.forEach runs your function for each element in the array. Setting the value of i does nothing, it's not a reference.

Just use a normal for loop:

for(var i = 0; i < arr.length; i++){
    arr[i] = 0;
}

Or instead of doing new Array(10), you can just do:

var arr = [];
for(var i = 0; i < 10; i++){
    arr[i] = 0;
}

4 Comments

I like this answer over Qambar's. KISS.
It's true that setting the value of i would do nothing, as it's not a reference. In this case, however, it doesn't even get that far due to the elision described in Adam's answer. Here's an example.
@SimonMᶜKenzie: Thanks. I didn't realize that the function wouldn't even run because the value is undefined.
@RocketHazmat thats not true, you can forEach an array filled with undefined values
5

Based on Faris Zacina answer, a one line approach.

var zeros = Array.apply(null, Array(10)).map(function(){return 0})

3 Comments

what is this for please?
This creates an array with ten zeros inside. First, Array(10) creates an array with ten "empty" places, since empty places are not iterable, we use Array.apply which is used to bind a this object, which we are setting to null, and supply arguments as an array. All this creates an array of undefineds which now is iterable and we can fill it with map
oh.. trying to win some points aren't you..
2

Controlling what is 'elided'

As mentioned, the key being present is what determines if forEach hits, and the length just decides the range of keys to check.

Another interesting perspective on this is that you can control what is visible to forEach and other iterative methods using a Proxy with has trap.

here we filter the odd indicies.

let arr = ['zero', 'one', 'two', 'four']
arr.length = 6

let arrProxy = new Proxy(arr, { has(arr, k){ return k%2===0 } })

arrProxy.forEach( val => { console.log(val) })
//zero
//two
//undefined

Such a strategy also lets you get to other behaviors,

if instead of k%2===0 you had

  • true you would hit all the empty space.
  • k != undefined lets you iterate over defined values only.
  • k in arr takes you back where you started.

of course you should use filter in most situations, this is just a demonstration.

Comments

1

I hope someone can correct me if i'm wrong about this

Inside the V8 source code array.js:1042, I found this:

for (var i = 0; i < length; i++) {
      if (i in array) {
        var element = array[i];
        f(element, i, array);
      }
      //...

The important bit is the condition check using the 'in' operator to determine whether or not to execute the function argument passed into the forEach. The in operator checks for the existence of properties inside an object.

Now a JS array is simply a fancy object with numbered properties. Ie

var array = [undefined,undefined,undefined];
Object.keys(array); // ["0", "1", "2"]

Another way of thinking about the above array is by thinking of it as an object like this

{
  "0": undefined,
  "1": undefined,
  "2": undefined
}

However if you use the other form for constructing the array, you get this:

var array2 = new Array(3);
Object.keys(array2); // []

This is why you will get the following result:

var array = [undefined,undefined,undefined];
var array2 = new Array(3);

array.forEach(function(){console.log('foo')}); //will print foo three times
array2.forEach(function(){console.log('bar')}); //will print nothing

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.