This question already has answers here:
Undefined values in Array(len) initializer
(5 answers)
Closed 7 years ago.
I would expect the following code to return [1,1,1,1...]
(new Array(10)).map(function() { return 1;})
but it returns [, , , , , ...].
Moreover,
(new Array(10)).length == 10 and (new Array(10))[0] == undefined are true.
And for z = function(){return 0;}; the expression z(undefined) === 0 is also true.
Yet I have noticed that [,,,,,,,,,,].map(function() { return 1; }) also returns [,,,,....].
Can anyone explain why?
So. I would expect the following code to return [1,1,1,1...].
(new Array(10)).map(function() { return 1;})
But it returns [, , , , , ...].
Right, because new Array(10) creates an array with no elements with a length of 10, and map only iterates over elements that actually exist. (And yes, this is surprising. :-) )
Moreover, (new Array(10)).length == 10 and (new Array(10))[0] == undefined are true.
Again correct, because (again) new Array(10) doesn't put any elements in the array, so accessing [0] gives you undefined.
JavaScript's standard arrays aren't really arrays at all, and they can have a length that's a positive number without having any entries in them. They're a form of "sparse" array.
Let's take a simpler example:
var a = new Array(10);
a[2] = 1;
That array contains one element, the element at index 2. There is no element at index 0, no element at index 1, and no elements at indexes 3 and above. It just has gaps there. You can tell by asking it:
console.log(0 in a); // "false"
console.log(1 in a); // "false"
console.log(2 in a); // "true"
Standard arrays in JavaScript are just objects with special behavior assigned to length, special behavior assigned to a class of property names (loosely, numeric ones), and that are backed by Array.prototype.
This is all in contrast to the newer "typed" arrays, Int32Array and such, which are true arrays in the traditional sense.
This will work:
Array.apply(null, Array(10)).map(…
Live demo: http://jsfiddle.net/xXG5p/
You have to make the array dense first. new Array(n) creates a sparse array. Sparse arrays do have a length but they have no elements. Hence, a .map() call will not iterate at all.
Using my code above, you will create a dense array that does contain elements (whose values are initially set to null).
Here is what the Mozilla Developer Network says about Array.prototype.map:
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.
When you call new Array(10), the Array you create believes it is 10-element long but none of its indexes have ever been assigned to. Here's an example of what happens if you use a literal array for which you have not set values for some indices:
[1, , 3].map(function () {return "a"})
The value you get is:
[ 'a', , 'a' ]
Because at index 1, no value was assigned.
Related
I'm quite confused by these little guys. After I encountered some funny behavior between them and Array.prototype.filter I fooled around in re.pl trying to understand their true value. But it seems like they switch from <empty> to undefined depending on who's looking (at least in re.pl and node, they're logged as undefined in this environment).
let emptyArr = new Array(5);
//set up two control elements
emptyArr[0] = 0;
emptyArr[4] = undefined;
console.log('\nemptyArr:', emptyArr)
console.log('\npeeking at an empty element:', emptyArr[1])
console.log('\nfilter for undefined elements:', emptyArr.filter(e => e === undefined))
console.log('\nfilter for any element:',
emptyArr.filter(e => {
console.log("ele:", e)
return true
})
) // only two elements are registered here
console.log('\nmappedEmpty:', emptyArr.map(e => e)) //everything is preserved
console.log('\ngenerated array', Array.from(emptyArr))
console.log('\nalways true filter on generated array:', Array.from(emptyArr).filter(e => true)) // empties are now 'true' undefined
What's the story here? Quirky array prototype methods or a secret ultra-false-y value?
What's the story here? Quirky array prototype methods or a secret ultra-false-y value?
Arrays are objects. Elements of the array are simply properties of the underlying object. Accessing a property that doesn't exist returns undefined. Therefore when you access emptyArr[1] you get undefined. Looking at the console of Chrome might help:
As you can see, 0 and 4 exist because you created those entries by assigning to them. 1, 2 and 3 don't exist.
These positions with no value are often referred to as "holes". Your array has holes at positions 1, 2 and 3. An array with holes is also called "sparse array".
Most array methods (.filter, .map, etc) skip over holes. We can easily prove this for some methods:
// Array#map
console.log([,,,42].map(() => 21)); // [,,,21], not [21,21,21,21]
// Array#every
console.log([,,,42].every(x => x === 42)); // true, not false
Of course we could also just look at the language specification, where it says for Array#every for example:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
Array.from on the other hand explicitly looks at the .length property of the value passed to it and it will copy any property/element between 0 and .length. In other words, it does not skip holes.
Look at the difference between the arrays in the Chrome console:
Worth noting maybe that arr.length doesn't care about holes. It will always be the highest set index in the array + 1.
Well actually they do not exist. Imagine arrays being objects, using new Array(5) you will get:
{
length:5
}
So like objects they return undefined for nonset values:
array[3] // undefined
But that doesnt mean theres a property set to undefined. That changes when you use Array.from . You can imagine it doing sth like
for(var i = 0; i < oldarray.lemgth; i++){
newarray[i] = oldarray[i];
}
So after the Array.from call itll look like this:
{
length:5,
0:undefined,
1:undefined,
2:undefined,
3:undefined,
4:undefined
}
They are undefined. It just depends on the engine how it displays them, but trying to access them returns undefined, because that's what they are - elements that have not been defined.
For example the Chrome console will print > (5) [empty × 5] while node prints [ , , , , ] for the same new Array(5). It's just a more visual representation than, say, showing [undefined, undefined, undefined, undefined, undefined].
This question already has answers here:
JavaScript "new Array(n)" and "Array.prototype.map" weirdness
(14 answers)
Why does Array.apply(null, [args]) act inconsistently when dealing with sparse arrays?
(2 answers)
Difference between Array.apply(null, Array(x) ) and Array(x)
(5 answers)
Closed 5 years ago.
I've recently discovered that mapping an uninitialised array doesn't seem to work as I would expect. With this code:
function helloMap(value, index) {
return "Hello " + index;
}
console.clear();
var initialArray = Array.apply(null, new Array(5));
console.log("Array initialised with apply:");
console.log(initialArray);
console.log(initialArray.map(helloMap));
var normalArray = new Array(5);
console.log("Array constructed normally");
console.log(normalArray);
console.log(normalArray.map(helloMap));
.as-console-wrapper {
max-height: 100% !important;
}
I get different results despite the first output for each array being [undefined, undefined, undefined, undefined, undefined].
The fact that I get different results implies that the undefined in these 2 arrays are in fact different. In the first I suspect that the array has 5 items in, each one is undefined. In the second the array is 5 items long but there is nothing, not even undefined in there...
It's a bit confusing.
Can someone explain it to me?
Array.apply(null, Array(5)) actually fills the array (or array-like object) that you pass as the second argument with the value of the first argument you pass in, as can be seen in the MDN Docs.
new Array(5) is just initializing an array with it's length property set to the argument of 5. Again, as can be seen in the MDN docs:
If the only argument passed to the Array constructor is an integer between 0 and 232-1 (inclusive), this returns a new JavaScript array with its length property set to that number (Note: this implies an array of arrayLength empty slots, not slots with actual undefined values).
According to MDN Array.prototype.map
map calls a provided callback function once for each element in an
array, in order, and constructs a new array from the results. callback
is invoked only for indexes of the array which have assigned values,
including undefined. It is not called for missing elements of the
array (that is, indexes that have never been set, which have been
deleted or which have never been assigned a value).
Both the arrays are different in the way Array.map executes the callback.
Since the array in second scenario doesn't have indexes map is returning empty
REASON
The answer lies in Array constructor
In the first scenario you are passing an array with length 5 to Array constructor which will index the array based on the length but in the second scenario you are just using the array with length 5
You will get to know the difference when you run Object.keys(initialArray) with Object.keys(normalArray)
Try checking the below example.
function helloMap(value, index) {
return "Hello " + index;
}
console.clear();
var initialArray = Array.apply(null, new Array(5));
console.log("Array initialised with apply:");
console.log(initialArray);
console.log(initialArray.map(helloMap));
var normalArray = new Array(5);
console.log("Array constructed normally");
console.log(normalArray);
console.log(normalArray.map(helloMap));
//DIFFERENCE
console.log("Initial Array: "+Object.keys(initialArray));
console.log("Normal Array: "+Object.keys(normalArray));
.as-console-wrapper {
max-height: 100% !important;
}
map is not called on any elements of normalArray because there are no elements. Normal array is an array with 5 empty slots.
initialArray has 5 slots filled with undefined
I think here is your answer, mozilla developers
console.log(normalArray.map(helloMap)); will always fail, because it runs in the context - creates new array but won't work with undefined values.
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
What exactly is the difference between:
Array(3)
// and
Array.apply(null, Array(3) )
The first returns [undefined x 3] while the second returns [undefined, undefined, undefined]. The second is chainable through Array.prototype.functions such as .map, but the first isn't. Why?
There is a difference, a quite significant one.
The Array constructor either accepts one single number, giving the lenght of the array, and an array with "empty" indices is created, or more correctly the length is set but the array doesn't really contain anything
Array(3); // creates [], with a length of 3
When calling the array constructor with a number as the only argument, you create an array that is empty, and that can't be iterated with the usual Array methods.
Or... the Array constructor accepts several arguments, whereas an array is created where each argument is a value in the array
Array(1,2,3); // creates an array [1,2,3] etc.
When you call this
Array.apply(null, Array(3) )
It get's a little more interesting.
apply accepts the this value as the first argument, and as it's not useful here, it's set to null
The interesting part is the second argument, where an empty array is being passed in.
As apply accepts an array it would be like calling
Array(undefined, undefined, undefined);
and that creates an array with three indices that's not empty, but have the value actually set to undefined, which is why it can be iterated over.
TL;DR
The main difference is that Array(3) creates an array with three indices that are empty. In fact, they don't really exist, the array just have a length of 3.
Passing in such an array with empty indices to the Array constructor using apply is the same as doing Array(undefined, undefined, undefined);, which creates an array with three undefined indices, and undefined is in fact a value, so it's not empty like in the first example.
Array methods like map() can only iterate over actual values, not empty indices.
The .map() API does not iterate over completely uninitialized array elements. When you make a new array with the new Array(n) constructor, you get an array with the .length you asked for but with non-existent elements that will be skipped by methods like .map().
The expression Array.apply(null, Array(9)) explicitly populates the newly-created array instance with undefined, but that's good enough. The trick is whether or not the in operator will report that the array contains an element at the given index. That is:
var a = new Array(9);
alert(2 in a); // alerts "false"
That's because there really is no element at position 2 in the array. But:
var a = Array.apply(null, Array(9));
alert(2 in a); // alerts "true"
The outer call to the Array constructor will have explicitly populated the elements.
This is an artifact of how apply works. When you do:
new Array(9)
an empty array is created with a length of 9. map does not visit non–existent members, so does nothing at all. However, apply turns the array into a list using CreateListFromArrayLike so it turns the formerly empty array into a parameter list like:
[undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined];
that is passed to Array to create an array with 9 members, all with a value of undefined. So now map will visit them all.
BTW, ECMAScript 2015 has Array.prototype.fill for this (also see MDN) so you can do:
Array(9).fill(0);
Because the first array would not have ordered properties arr[0] === undefined and the second does. Array functions like forEach and map will iterate from 0 to the array's length - 1 and the lack of order to the properties of the first is an issue. The second version produces an array with the correct ordering, i.e.
arr = Array.apply(null, Array(3));
arr[0] === undefined //true
arr[1] === undefined //true
//etc.
The first version as you noticed doesn't. Also, adding new to the first version would not make it work.
In the first case you have one operation
Array(3)
Its creates an array with three empty slots. Not an array with the three undefined values but exactly - empty.
At the second case
Array.apply(null, Array(3) )
we can spread it to the three operations:
first: Array(3) - you get an array with 3 empty slots;
second: Array(3) spreads by Function.prototype.apply() function to 3 parameters that it passes to Array() function. At this stage 3 empty slots in given array transformes by apply() to 3 undefined values (it looks like if apply() sees an empty slot it automaticaly turns it to undefined in any sparsed array).
third: we get an Array(undefined, undefined, undefined). And that will do to us an array with 3 undefined (not empty) values.
Because now you have 3 undefined but not empty slots, you can use them with map() function.
Note that not only Function.prototype.apply() have such behavior of decomposing arrays by such way. You can also do this in ECMAScript 6 by "..." - spread operator.
Array(...new Array(3));
This will also returns an array with 3 undefined and respectively can be mapped slots.
Here i giving more detailed explanation.
https://stackoverflow.com/a/56814230/11715665
Today I stumbled upon a question here on Stack Overflow - How do I remove objects from a javascript associative array?. What struck me was that the accepted answer was both misleading and heavily upvoted, so I highlighted the possible pitfall.
However, while cobbling together a corrective answer, I realized I have no idea as to why it makes sense for delete to keep elements assign undefined instead of removal.
var elements = new Array()
elements.push(NaN)
elements.push(NaN)
elements.push(NaN)
delete elements[1]
console.log("number of elements: ", elements.length) // returns 3
Is there a rationale behind it?
I realized I have no idea as to why it makes sense for delete to assign undefined instead of removal.
It doesn't. delete removes properties from objects, it does not set them to undefined. Here's an easy way to tell:
var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
delete a[1];
console.log(1 in a); // logs "false"
Note that after the delete, a doesn't have a property called 1 anymore. At all.
Contrast with:
var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
a[1] = undefined;
console.log(1 in a); // logs "true"
There, a still has a property called 1, it's just that the property's value is undefined.
It's useful to understand that in JavaScript, arrays aren't really arrays at all. They're just objects, array "indexes" are just property names (which are strings — yes, really, we just tend to write them as numbers), arrays have special handling of property names that are all numeric (indexes), a special length property, and some functions they get from Array.prototype. This is very clearly laid out in Section 15.4 of the spec. Once you have it set firmly in your head that JavaScript arrays aren't really arrays, they make a lot more sense. :-)
Deleting an array "index" property from an array does not change its length (not even if you delete the highest-numbered one); it just creates a hole in the array (JavaScript "arrays" are sparse arrays by their nature; e.g., they can have gaps in them). So in my first example above, I get exactly the same array that I'd've gotten if I'd done this:
var a = [];
a[0] = 'a';
a[2] = 'c';
Note the gap, the array has no 1 element/property.
If you say:
var foo = a[3];
...foo can get the value undefined for two completely different reasons:
a has a property called 3 that has the value undefined, or:
a has no property called 3 at all; the result of a property accessor operation on an object that doesn't have a property by that name is undefined. This if-no-property-return-undefined is covered by the spec in a fairly convoluted way, but mostly in Section 8.12.3.
These are very distinct things.
It doesn't assign undefined. It deletes the property. (If you try to access a property that doesn't exist, you will get undefined, and length is based on the highest numbered item in the array).
It makes sense, because it works that way on any kind of object. For it to act otherwise, it would have to special case objects if they were an instanceof Array but only if it was a property with a numeric name.
Use splice if you want to remove an item from an array.
> var elements = [NaN, NaN, NaN];
> elements.splice(1,1);
> console.log(elements);
[ NaN, NaN ]
JavaScript arrays can be "sparse". That is, some slots can be empty in the sense of never having had a value assigned, or having a value deleted. If you test the value associated with that index you'll get back undefined because it doesn't exist, not because it was assigned the value undefined.
When delete removes an item from an array it doesn't automatically slide the rest of the elements up to fill the space: the other elements retain their existing indexes, which in turn means the .length property doesn't change since .length is defined as being equal to the highest assigned index plus one.
If you want to remove an array element and have the other elements renumbered use the .splice() method.
This is because delete operator removes property, and removed property has value undefined. To remove element from array you can use splice method of array.
All of this is because of how the delete operator in javascript work.
When you do not set property of object and try to check its value it will be undefined:
var obj = {};
alert(obj.foo); // undefined
which is the same as:
alert(obj['foo']); // undefined
And look at this:
// create empty object
var obj = {};
// check its property named 1
alert(obj[1]); // undefined
// set property named 1 to value 'fooo'
obj[1] = 'fooo';
// and check it
alert(obj[1]); // 'fooo'
// now delete it
delete obj[1];
// and check again
alert(obj[1]); // undefined
delete has removed property and its value is undefined - and all above code was about object.
Now look at the arrays:
var arr = []; // it's better to use [] than new Array();
alert(arr[1]); // undefined - same as above
// assign value
arr[1] = 'fooo'
// check it
alert(arr[1]); // 'fooo' - same as above
// remove it
delete arr[0];
// and check
alert(arr[1]); // undefined - same as above
So behavior is the same, but what about length property of array. Specification http://es5.github.com/#x15.4.5.2 says:
"The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index."
So when you look at this:
var arr = ['foo', 'bar', 'foobar'];
alert(arr.length); // 3
// delete first
delete arr[1];
// and check length
alert(arr.length); // 3
Last check gives 3 because the last deletable property in this array has index 2 - first property (with index 0) has value undefined (delete operator set this value), second item (with index 1) has value 'bar' and third (with index 2) has value 'foobar'. So according to specification length = 2 + 1 ('allways numerically greater than last deletable');
This is visible also in this code:
var arr = [];
arr[10] = 'foo';
// element with index 10 is set to 'foo' but elements from 0 to 9 don't have value - they are undefined
// now check length
alert(arr.lenght); // 11
Last deletable property index is 10, so 10 + 1 gives 11, despite of previous elements that are undefined.
So delete operator does his work, but it is not designed to remove items from array.
Could someone explain this (strange) behavior? Why is the length in the first example 3 and not 2, and most importantly, why is the length in the second example 0? As long as the keys are numerical, length works. When they are not, length is 0. How can I get the correct length from the second example? Thank you.
a = [];
a["1"] = {"string1":"string","string2":"string"};
a["2"] = {"string1":"string","string2":"string"};
alert(a.length); // returns 3
b = [];
b["key1"] = {"string1":"string","string2":"string"};
b["key2"] = {"string1":"string","string2":"string"};
alert(b.length); // returns 0
One thing to note is that there is a difference between regular arrays and associative arrays. In regular arrays (real arrays), the index has to be an integer. On the other hand, associative arrays can use strings as an index. You can think of associative arrays as a map if you like. Now, also note, true arrays always start from zero. Thus in your example, you created an array in the following manner:
a = [];
a["1"] = {"string1":"string","string2":"string"};
a["2"] = {"string1":"string","string2":"string"}
Javascript was able to convert your string indexes into numbers, hence, your code above becomes:
a = [];
a[1] = {"blah"};
a[2] = {"blah"};
But remember what i said earlier: True arrays start from zero. Therefore, the javascript interpreter automatically assigned a[0] to the undefined. Try it out in either firebug or the chrome/safari console, and you will see something like this when you try to print "a". You should get something like "[undefined, Object, Object]. Hence the size 3 not 2 as you expected.
In your second example, i am pretty sure you are trying to simulate the use of an associated array, which essentially is adding properties to an object. Remember associated arrays enable you to use strings as a key. So in other terms, you are adding a property to the object. So in your example:
b["key1"] = {"string1":"string","string2":"string"};
this really means:
b.key1 = {"string1":"string","string2":"string"};
Initializing b =[] simply creates an array, but your assignment doesn't populate the array. It simply gives "b" extra properties.
length returns 1 + the largest integer key in the object.
In a the largest key is 2 so 1+2 is 3.
In b there are no integer keys (the keys there are key1 and key2 which cannot be converted into ints) so Javascript assumes that the largest key is -1, and 1 + -1 yields 0.
This program will help you see that:
a = [];
a["1"] = {};
a["4"] = {};
alert(a.length); // Prints 5
From the ECMAScript standard, ECMA-262, 5th ed.
15.4.5.2 length
The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index.
Note the length property of an array only takes into account array indices, which are integers; setting other properties doesn't affect length.
For an array, a["3"] is equivalent to a[3] (this behavior is specified by § 15.4.5.1); 3 is an array index rather than a property. Thus setting a["3"] affects the array's length. b["key1"] is equivalent to b.key1. Setting properties don't affect the length of a collection.