Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined values with new Array() in JavaScript

Looking at some javascript code, I saw (something like) this:

var arr = Array.apply(null, {length: 10});

Reading the MDN documentation for Function.prototype.apply(), I learnt that although it usually expects an array as its second argument which is an array of the arguments to be passed to the invoked function,

you can also use any kind of object which is array-like, so in practice this means it's going to have a property length and integer properties in the range (0...length).

So from what I could tell, it's calling Array() as if it was passed 10 arguments, but since it doesn't define any of those "integer properties", it's as if it was passed 10 undefined arguments. Is that correct?

console.log(arr);

yields

[ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined ]

Which is totally different to the result of

var barr = new Array(10);
console.log(barr);

which is

[ , , , , , , , , , ]

These arrays also behave differently.

console.log(arr.map(function(item) { return 'hi';}));
console.log(barr.map(function(item) { return 'hi';}));

gives

[ 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi' ]

[ , , , , , , , , , ]

I wanted to know why the map function didn't work for the second one. So I checked console.log(1 in arr); which gave true and `console.log(1 in barr);' which gave false.

So my current guess is that arr is an array which contains as integer properties 10 variables which each have the value of undefined and barr is an array which, although it has a length of 10, has no integer properties. And Array.apply works because it asks {length: 10} what its property 0 is, receives undefined, and so assigns undefined to the property 0 of the array it's constructing, and so on. Is my reasoning correct? And is there really no less hackish way to define an array which contains undefined integer properties for each index within its range, rather than having no integer properties at all? Because looking at this, it seems to me that new Array() is pretty useless.

like image 538
PoolOfPeas Avatar asked Jan 30 '16 09:01

PoolOfPeas


2 Answers

So from what I could tell, it's calling Array() as if it was passed 10 arguments, but since it doesn't define any of those "integer properties", it's as if it was passed 10 undefined arguments. Is that correct?

Yes, exactly. It's doing this:

var arr = Array(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
var barr = new Array(10);
console.log(barr);

...

console.log(arr.map(function(item) { return 'hi';}));
console.log(barr.map(function(item) { return 'hi';}));

I wanted to know why the map function didn't work for the second one.

Because map, forEach, and similar only visit properties that actually exist. As you said, there's a big difference between.

var arr = Array(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
// Or `var arr = Array.apply(null, {length: 10});

and

var barr = new Array(10);

In the first example, arr has 10 entries, each of which has the value undefined. In the second example, barr has no entries and a length of 10. So in the first one, map will visit the properties, because they exist. In the second one, it won't, because they don't.

Recall that standard arrays in JavaScript aren't really arrays at all (disclosure: that's a post on my blog), they're objects with some special behavior. As such, they're inherently sparse:

var a = [];
a.length = 10000;

a doesn't have 10,000 entries. It has no entries. It's just that its length property is 10000.

You can tell whether the property exists by using hasOwnProperty or in. Compare:

var barr = new Array(10);
console.log(barr.hasOwnProperty(0)); // false
console.log(0 in barr);              // false

to:

var arr = Array.apply(null, {length: 10});
console.log(arr.hasOwnProperty(0));  // true
console.log(0 in arr);               // true

And Array.apply works because it asks {length: 10} what its property 0 is, receives undefined, and so assigns undefined to the property 0 of the array it's constructing, and so on. Is my reasoning correct?

Yes, although to be clear, it's apply that's asking what property 0 is, and then it's using that as the first argument when calling Array. It's Array that then takes the first argument's value and assigns it to the 0 property on the array it's creating.

And is there really no less hackish way to define an array which contains undefined integer properties for each index within its range, rather than having no integer properties at all?

Only slightly: ES2015 adds Array.from, which accepts an array-like object and returns a true array (optionally mapping the entries). So that would be:

var arr = Array.from({length:10});

It's rare to need to do that, as opposed to simply a = new Array(bigNumberHere); or a = []; a.length = bigNumberHere. E.g., many times you don't care if the property doesn't exist or exists with the value undefined. Sometimes you do, but to give you perspective, I've been writing JavaScript professionally for 20 years, fairly intensively the last 8, and I've probably cared, oh, once or twice, tops.

You mentioned that in the specific case you were dealing with, it was combined with map, so Array.from would take the place of both:

var arr = Array.from({length: 10}, function() { return "hi"; });

...yields

["hi", "hi", "hi", "hi", "hi", "hi", "hi", "hi", "hi", "hi"]

Although Array.from is new in ES2015, it can be shimmed/polyfilled on older JavaScript engines.

like image 102
T.J. Crowder Avatar answered Sep 29 '22 11:09

T.J. Crowder


Is that correct?

Yes

you can also use any kind of object which is array-like, so in practice this means it's going to have a property length and integer properties in the range (0...length).

It says that you can pass a duck-type of an array: an object that has a sort of interface:

  1. a length property (for iteration)
  2. optional: some integer properties (the values)

This will be treated as an Array. So passing a { length: 10 } is like pass a real array [undefined,undefined,undefined,..] where each index is created and has value undefined.

So the first code row:

var arr = Array.apply(null, {length: 10});

is interpreted as:

var arr = Array(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined)

So my current guess is that arr is an array which contains as integer properties 10 variables which each have the value of undefined and barr is an array which, although it has a length of 10, has no integer properties.

Yes.

var test = Array(10);

it outputs: [undefined x10] <-- no indexes, only a simple property 'length' unaligned with the real content, but useful for retrieve it and do some size condiderations.

test[2] = undefined;

it outputs: [undefined x2, undefined, undefined x7] <-- no indexes except for the position '2' that has a value: undefined.

like image 26
Luca Mazzanti Avatar answered Sep 29 '22 12:09

Luca Mazzanti