17

I am confused by the results of mapping over an array created with new:

function returnsFourteen() {
    return 14;
}

var a = new Array(4);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
a.map(returnsFourteen);
> [undefined x 4] in Chrome, [, , , ,] in Firefox

var b = [undefined, undefined, undefined, undefined];
> [undefined, undefined, undefined, undefined]
b.map(returnsFourteen);
> [14, 14, 14, 14]

I expected a.map(returnsFourteen) to return [14, 14, 14, 14] (the same as b.map(returnsFourteen), because according to the MDN page on arrays:

If the only argument passed to the Array constructor is an integer between 0 and 2**32-1 (inclusive), a new JavaScript array is created with that number of elements.

I interpret that to mean that a should have 4 elements.

What am I missing here?

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
  • 1
    `.map()` works not in a *destructive* manner, try to assign the result back to `a` – jAndy Jul 27 '12 at 15:05
  • The weird part is that if you do `console.log(a, b);` **before** any mapping it returns: `[undefined × 4] [undefined, undefined, undefined, undefined]` (in Chrome) – Naftali Jul 27 '12 at 15:06
  • 1
    @Neal: that is just `console` sugar. In reality, we have an `array` with a `length` of 4 – jAndy Jul 27 '12 at 15:07
  • @jAndy I understand that, the question is about the **return value**. – Matt Fenwick Jul 27 '12 at 15:07
  • 1
    @jAndy ehhh they _should_ log the same thing, no? – Naftali Jul 27 '12 at 15:08
  • @Neal: agreed, this behavior somehow gives the sense that some "instances" of "undefined" are less defined than others... =| – maerics Jul 27 '12 at 15:11
  • @Felix I would say this question is similar, but *not* identical because the crux of my problem is interpreting the MDN documentation. – Matt Fenwick Jul 27 '12 at 15:30
  • 3
    @Matt: Then IMO this is not a good question for SO. You pointed out a controversial passage in the documentation, which is good, but can also be fixed and then the problem does not exist anymore. This is more suited as some kind of bug report or a discussion somewhere else IMO (for example: https://developer.mozilla.org/Talk:en/JavaScript/Reference/Global_Objects/Array ). – Felix Kling Jul 27 '12 at 15:34

4 Answers4

15

When you create an array like so:

var arr1 = new Array( 4 );

you get an array that has a length of 4, but that has no elements. That's why map doesn't tranform the array - the array has no elements to be transformed.

On the other hand, if you do:

var arr2 = [ undefined, undefined, undefined, undefined ];

you get and array that also has a length of 4, but that does have 4 elements.

Notice the difference between having no elements, and having elements which values are undefined. Unfortunately, the property accessor expression will evaluate to the undefined value in both cases, so:

arr1[0] // undefined
arr2[0] // undefined

However, there is a way to differentiate these two arrays:

'0' in arr1 // false
'0' in arr2 // true
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • additionally, most `console` objects behave really *confusing* on `Arrays` which just have a defined `.length`, but without real properties. I guess that should be part of the answer. – jAndy Jul 27 '12 at 15:12
  • 3
    So is the quote from the MDN docs -- `a new JavaScript array is created with that number of elements` -- wrong, or have I misinterpreted it? To me, it seems to mean that there actually are elements with values. – Matt Fenwick Jul 27 '12 at 15:14
  • @MattFenwick: can you please link to the quote – jAndy Jul 27 '12 at 15:14
  • 2
    @MattFenwick Yes, it is wrong. The newly created array does not have any elements. (Elements being own properties which names are indexes). – Šime Vidas Jul 27 '12 at 15:18
  • @jAndy Yea, my Firebug prints `[ undefined, undefined, undefined, undefined ]` for both `arr1` and `arr2` (from my answer), which implies that both arrays have 4 elements. It'd be nice if they fixed that. – Šime Vidas Jul 27 '12 at 15:26
10
var a = new Array(4);

This defines a new array object with an explicit length of 4, but no elements.

var b = [undefined, undefined, undefined, undefined];

This defines a new array object with an implicit length of 4, with 4 elements, each with the value undefined.

From the docs:

callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

For array a, there are no elements that have been assigned values, so it does nothing.

For array b, there are four elements that have been assigned values (yes, undefined is a value), so it maps all four elements to the number 14.

jbabey
  • 45,965
  • 12
  • 71
  • 94
  • So is the quote from the MDN docs -- `a new JavaScript array is created with that number of elements` -- wrong, or have I misinterpreted it? To me, it seems to mean that there actually are elements with values. – Matt Fenwick Jul 27 '12 at 15:11
  • 1
    @MattFenwick the docs are correct. there is an important difference between an element *being* `undefined` and an element *having the value* `undefined`. – jbabey Jul 27 '12 at 15:14
  • 2
    @jbabey But `new Array(4)` creates an array with *no* elements, so the MDN docs are not correct... According to MDN, that array would have 4 elements. – Šime Vidas Jul 27 '12 at 15:21
  • @ŠimeVidas it does create an array with 4 elements, but those 4 elements do not have any values assigned. map will not iterate over elements that have never been assigned values. – jbabey Jul 27 '12 at 15:50
  • 1
    @jbabey I don't agree. An array element is, [by definition](http://ecma-international.org/ecma-262/5.1/#sec-15.4), a property which property name is an array index (a non-negative integer number, or a string representing such a number: `'1'`, `'2'`, `'3'`, etc.). The [algotithm for `new Array(len)`](http://ecma-international.org/ecma-262/5.1/#sec-15.4.2.2) does not specify that any elements are added to the newly created array. – Šime Vidas Jul 27 '12 at 16:51
  • 1
    In addition, when `Object.getOwnPropertyNames` is invoked with such array, the result will contain the `'length'` string, but no array index. Therefore, the array does not contain any elements, but merely the `'length'` property. Take a look at @jAndy 's answer for details. – Šime Vidas Jul 27 '12 at 16:52
5

new Array(len) creates an empty array, and does something different than filling it with undefined values: It sets its length to len. So, it translates to this code:

var newArr = [];
newArr.length = len;

Let's have some fun with newArr (assuming that len = 4):

newArr.length; //4
newArr[1] === undefined; //true
newArr.hasOwnProperty(1); //false

This is because while the is 4 items long, it does not contain any of these 4 items. Imagine an empty bullet-clip: It has space for, say, 20 bullets, but it doesn't contain any of them. They weren't even set to the value undefined, they just are...undefined (which is a bit confusing.)

Now, Array.prototype.map happily walks along your first array, chirping and whistling, and every time it sees an array item, it calls a function on it. But, as it walks along the empty bullet-clip, it sees no bullets. Sure, there are room for bullets, but that doesn't make them exist. In here, there is no value, because the key which maps to that value does not exist.

For the second array, which is filled with undefined values, the value is undefined, and so is the key. There is something inside b[1] or b[3], but that something isn't defined; but Array.prototype.map doesn't care, it'll operate on any value, as long as it has a key.

For further inspection in the spec:

Zirak
  • 38,920
  • 13
  • 81
  • 92
  • As I've asked the other answerers -- is the quote (`a new JavaScript array is created with that number of elements`) from the docs wrong or have I misinterpreted it? – Matt Fenwick Jul 27 '12 at 15:24
  • @MattFenwick It's partially correct, which means it's mostly wrong and misleading. There is the capacity for that number of elements (there always is), but the elements themselves don't exist there. – Zirak Jul 27 '12 at 15:28
1

One additional answer on the behavior of console.log. Plain simple, the output is sugar and technically wrong.

Lets consider this example:

var foo = new Array(4),
    bar = [undefined, undefined, undefined, undefined];

console.log( Object.getOwnPropertyNames(bar) );
console.log( Object.getOwnPropertyNames(foo) );

As we can see in the result, the .getOwnPropertyNames function returns

["length"]

for the foo Array/Object, but

["length", "0", "1", "2", "3"]

for the bar Array/Object.

So, the console.log just fools you on outputting Arrays which just have a defined .length but no real property assignments.

jAndy
  • 231,737
  • 57
  • 305
  • 359
  • Actually, the console output wasn't a problem on my browsers. The real issue was my misunderstanding/confusion of the MDN docs. – Matt Fenwick Jul 27 '12 at 15:28