38

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.

Craig
  • 1,065
  • 3
  • 12
  • 26
  • There is a popular dup somewhere... `Array.apply(0, Array(10)).map(function(){return 0})` – elclanrs Dec 11 '14 at 21:53
  • That's a strange result, I would have thought you had an empty array with a length of 10 ? – adeneo Dec 11 '14 at 21:55
  • 2
    @adeneo: It'd be an array of 10 `undefined`s. – gen_Eric Dec 11 '14 at 21:56
  • @RocketHazmat - of course it would, I'm confused! – adeneo Dec 11 '14 at 21:58
  • @adeneo: I don't know why he's showing as just commas. Maybe that's how some browser is showing it. – gen_Eric Dec 11 '14 at 21:59
  • @RocketHazmat - That's what I was thinking of, I didn't bother testing, but I'm pretty sure my browser console would just give `[]`, and as now noted below, `forEach` would never iterate that array. – adeneo Dec 11 '14 at 22:00
  • 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> ]`. – James Montagne Dec 11 '14 at 22:02

6 Answers6

29

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]
Faris Zacina
  • 14,056
  • 7
  • 62
  • 75
25

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

Adam
  • 2,027
  • 1
  • 16
  • 27
  • 1
    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. – Faris Zacina Dec 11 '14 at 22:27
  • 2
    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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Description) reference. It _does_ visit elements which are `undefined`. – Simon MᶜKenzie Dec 13 '14 at 09:07
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;
}
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • 2
    I like this answer over Qambar's. KISS. – Mallen Dec 11 '14 at 22:03
  • 1
    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](http://jsfiddle.net/rombwtre/). – Simon MᶜKenzie Dec 11 '14 at 23:12
  • @SimonMᶜKenzie: Thanks. I didn't realize that the function wouldn't even run because the value is `undefined`. – gen_Eric Dec 12 '14 at 17:02
  • @RocketHazmat thats not true, you can forEach an array filled with undefined values – nikksan Oct 13 '18 at 14:14
5

Based on Faris Zacina answer, a one line approach.

var zeros = Array.apply(null, Array(10)).map(function(){return 0})
Mark E
  • 3,403
  • 2
  • 22
  • 36
  • what is this for please? – MartianMartian Mar 06 '18 at 16:44
  • 1
    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 `undefined`s which now is iterable and we can fill it with `map` – Mark E Mar 06 '18 at 16:52
  • 2
    oh.. trying to win some points aren't you.. – MartianMartian Mar 06 '18 at 16:54
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.

Space ED
  • 21
  • 2
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
derp
  • 2,300
  • 13
  • 20