create 3 undefined, empty array.
var a1 = [,,,];
var a2 = new Array(3);
from JavaScript: The Definitive Guide,
0 in a1 //true
0 in a2 //false
but, in real world browser, getting different result. (IE8 and chrome 33...)
0 in a1 //false
0 in a2 //false
which is true, book or real world?
Looks like the book is wrong. As you can see from the specification, [,,,] does not add any values to the array:
The production ArrayLiteral : [ Elisionopt ] is evaluated as follows:
Let array be the result of creating a new object as if by the expression new Array() where Array is the standard built-in constructor with that name.
Let pad be the result of evaluating Elision; if not present, use the numeric value zero.
Call the [[Put]] internal method of array with arguments "length", pad, and false.
Return array.
("Elisions" are the , which are not preceded or followed by an expression.)
In simpler terms:
Create an empty array
Evaluate the ,, which is basically just counting them, starting from 1.
So ,,, results in 3.
Then set the length of the array to the result (e.g. 3).
And that's exactly what new Array(3) is doing as well.
There is also a less formal description of elided elements:
Array elements may be elided at the beginning, middle or end of the element list. Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined. If an element is elided at the end of an array, that element does not contribute to the length of the Array.
The Array constructor (new Array( 3 )) does set the length attribute to 3 but does not create any members of the array – not even undefined values.
In cases when there is only one argument passed to the Array constructor and when that argument is a Number, the constructor will return a new sparse array with the length property set to the value of the argument. It should be noted that only the length property of the new array will be set this way; the actual indexes of the array will not be initialized.
Source: http://bonsaiden.github.io/JavaScript-Garden/#array.constructor
Testing that in Chromium does indeed result in two false values…
The in operator returns false if the given key does not exist:
var a1 = [1,,3,4];
0 in a1 // true
1 in a1 // false
Related
This question already has answers here:
What does [undefined × 2] mean and why is it different to [undefined,undefined]?
(1 answer)
What is "undefined x 1" in JavaScript?
(5 answers)
forEach on array of undefined created by Array constructor
(5 answers)
Closed 5 years ago.
What does the following actually create?
I ask because it cannot be enumerated with Array#foreach and it is represented (misleadingly?) in Chrome console as [undefined x 10].
const arr = new Array(10);
The Array object has three different possible constructor actions:
new Array()
new Array(<multiple arguments)
new Array(<one argument>) (placed as last because of its nature - see further)
The behavior of the constructor depends of the arguments provided to that constructor. All those three actions uses ArrayCreate during the constructor action. From the link:
The abstract operation ArrayCreate with argument length (a positive integer) and optional argument proto is used to specify the creation of new Array exotic objects.
This function accepts two arguments: len (length) and proto which is used to create an array object. (I won't go deeper here because it would be more complex). The len argument is more important in this situation. When we are looking to the constructor actions individually, you have
new Array()
Here, an object is constructed by ArrayCreate(0, proto), thus an empty array with .length = 0.
new Array(<multiple arguments>)
Here, an object is constructed by ArrayCreate(#of args, proto). If you provide 3 arguments, then you get an array with .length = 3. This array is then populated with the provided arguments. Do you give 5 arguments? You get .length = 5. So the .length property depends of the number of the arguments.
new Array(<one argument>)
This one is quite different. It has two cases. At init, an array object is created with ArrayCreate(0, proto) like if you're using new Array(). Still we have an argument here to use.
If the argument is a non-numeric value, then the effect is same as point 2. This argument is being added to the constructed object (like a .push() operation. So new Array('hello') or new Array('10') gives you an array object with .length = 1
Now, the thing is very different when you give a numeric argument new Array(10), which gives in Chrome [undefined x 10]. Why? Because when the argument is numeric (and valid), this function is being called: Set(O,P,V,Throw). This "magic" function sets the property P of object O with the value V. (The Throw is a flag to indicate " throw error or not? " question).
If the argument is a length, set(<constructed array>, 'length', <argument>, true) is being executed. So the .length property returns the argument value while there is nothing being preserved in the memory. End result: you have an object with a fake .length property... A side-effect of this is that using .push adds an element to the 10th index (if .length was 10). Now you have an array with 11 elements: 10 "empty" slots and an element that you've just pushed.
So what does the browsers do with an array with a "fake" .length property? They cannot iterate it correctly. So they provide a failsafe way: display "has 10 elements but are empty" like chrome does with [undefined x 10] or firefox: Array [ <10 empty slots>]
If you ask me why this is being standardized in the specs, then I would respond with "I don't know". It is one of the weirdest decision (among other ones that I have encountered) that is being standardized in ECMAScript.
The following two snippets are equivalent:
const arr = new Array(10)
and
const arr = [,,,,,,,,,,]
as can be demonstrated here:
const arr1 = new Array(10)
const arr2 = [,,,,,,,,,,]
let empty1 = true
for (let index = 0; empty1 && index < arr1.length; index++) {
if (arr1.hasOwnProperty(index)) {
empty1 = false
}
}
let empty2 = true
for (let index = 0; empty1 && index < arr2.length; index++) {
if (arr2.hasOwnProperty(index)) {
empty2 = false
}
}
console.log(empty1)
console.log(empty2)
This means that for all indices from [0, length - 1], they don't actually exist on the array because arr.hasOwnProperty(index) is false. This is distinguished from this example:
const arr = new Array(10).fill()
demonstrated here:
const arr = new Array(10).fill()
console.log(arr.every((_, index) => arr.hasOwnProperty(index)))
So if you want to initialize each index in a constructed array with undefined, just call .fill() without any arguments.
The Array constructor when supplied with a single integer value creates an Array instance with the length own-property set to that value.
Host environments like browsers can choose to render this as they choose. V8 chooses [undefined x 10] which is misleading because it implies there are ten instances of the predefined undefined value in an ordered sequence.
This is not the case.
Array#foreach, Array#map use the enumerable integer indices AND the length own-property value to enumerate the values of an array.
Because this specific Array constructor syntax does not initialize any integer properties on the Array object, they fail to enumerate Arrays initialized in this manner.
I can only presume that this syntax is soley present to enable code patterns that rely on the length own-property of the Array instance WITHOUT needing any keys or values to be initialized.
Trying to allocate new array with values.
Case 1 :
var x = new Array(3).map(()=>1);
Now x is [undefined * 3]
Case2 :
var x = [...new Array(3)].map(()=>1);
And now x is [1,1,1]
Can someone help here?
Why using this spread operator makes such a difference?
And why Case 1 doesn't work ?
tl;dr: The first array has holes, the second one doesn't. .map skips holes.
By using a spread element, the array is treated as an iterable, i.e. you get an iterator to iterate over the array. The iterator basically works like a for loop, it will iterate the array from index 0 to index array.length - 1 (see the spec for details), and add the value at each index to the new array. Since your array doesn't contain any values, array[i] will return undefined for every index.
That means, [...new Array(3)] results in an array that literally contains three undefined values, as opposed to just a "spare" array of length 3.
See the difference in Chrome:
We call "sparse arrays" also "arrays with holes".
Here is the crux: Many array methods, including .map, skip holes! They are not treating the hole as the value undefined, the completely ignore it.
You can easily verify that by putting a console.log in the .map callback:
Array(3).map(() => console.log('call me'));
// no output
And that's the reason your first example doesn't work. You have a sparse array with only holes, which .map ignores. Creating a new array with the spread element creates an array without holes, hence .map works.
Array
arrayLength
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 length set to that number.
new Array(3) does not actually create iterable values at created array having .length property set to 3.
See also Undefined values with new Array() in JavaScript .
You can use Array.from() at first example to return expected results
var x = Array.from(Array(3)).map(()=>1);
Spread operator
The spread operator allows an expression to be expanded in places
where multiple arguments (for function calls) or multiple elements
(for array literals) or multiple variables (for destructuring
assignment) are expected.
var x = [...new Array(10)].map(()=>1);
creates an array having values undefined and .length set to 10 from Array(10), which is iterable at .map()
Why case 1 doesn't work ?
Map function calls callback function in each element in ascending order
In your first case (breaking down..),
var x = new Array(3);
x = x.map( () => 1 );
x is an array with uninitialized index. Therefore, map function does not know where to start iterating your array from. And causing it to not iterating it at all (Which is not working).
You can test it by (in Chrome),
var x = new Array(5);
// This will display '[undefined x 5]' in chrome, because their indexes are uninitialized
x[1] = undefined;
// '[undefined x 1, undefined, undefined x 3]' Only array[1] that has its index.
// And you can use 'map' function to change its value.
x = x.map( () => 1 );
// '[undefined x 1, 1, undefined x 3]'
Why using this spread operator makes such a difference?
In your second sample,
Spread operator allows parts of an array literal to be initialized from an iterable expression
And enclose it in square bracket to properly use it.
So, [...new Array(2)] is actually an indexed array of [undefined, undefined].
Since your array in the following sample has been indexed. Let's have a look (in Chrome),
var x = [...new Array(2)];
// Now 'x' is an array with indexes [undefined, undefined]
x = x.map( () => 1 );
// Will return [1, 1]
Now, each value in x has its own indexes. Then finally, map function is able to iterate over it and call the callback function for each element in ascending order.
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
I have some wtfjs code:
var a = [,];
alert(a.indexOf(a[0]));
a.indexOf(a[0]) returns -1. The main point in this example is difference between uninitialized and undefined values:
a contains one not initialized element.
a[0] return undefined.
a don't contains the undefined value. So a.indexOf(a[0]) === -1 is true.
But where I can find the explanation why a[0] return undefined? What internal method is calling?
P.S. Undefined is the javascript primitive type. Uninitialized means the value that don't have any javascript type, but there is no such primitive type in javascript.
The ES5 spec tells us the following of array initialisers:
Elided array elements are not defined.
Note that they are not defined. That's different from having the value undefined. As you've already noticed, elided elements do contribute to the length of the array:
...the missing array element contributes to the length of the Array and increases the index of subsequent elements.
When you invoke indexOf on an array this is one of the steps that happens:
Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument ToString(k).
In that, k is a number corresponding to an array index and O is the array itself. Since elided elements were not defined the array does not have a property for the corresponding index.
The .indexOf() function only examines elements of the array that have explicitly been set. Therefore, in this case, even though the length of the array is 1 (or 2, depending on the browser involved), there are no explicitly-set elements, so the effective length is zero.
Another way of seeing this effect:
var a = [,];
console.log(a.length); // 1 (in Firefox)
console.log('0' in a); // false
That means that even though the length of the array is 1, there is no element with index 0. Thus, any explicit reference to examine the value of a[0] will have the value undefined.
Now, if we play a little more:
a[0] = undefined;
console.log(a.length); // still 1
console.log('0' in a); // true !!
Once the property reference has appeared on the left side of an assignment, it becomes "real" even if its (now explicit) value is undefined.
As for the "internal methods" involved, you can check the Reference Specification type, and in particular how its "Get" operation works.
This is a tricky one. For instance, if you set a = [,,1] and examine the array, you'll see that only the index of 2 has a value, and it is 1. Index 0 & 1 have no value at all, they were implicitly set to undefined. In other words, they are NOT DEFINED. If you search for a value of undefined in an array, it will always return -1. If you instead set them explicitly to null, you'll see your index come back.
To directly address your question, a.indexOf(a[0]) returns -1 because a[0] is undefined.
All elements of an array which are not defined as a value (including null) are undefined.
When you define an array like this:
var a = [,null,];
Elements a[0] and a[2] are undefined. Any index you use into the array a will return undefined except a[1] which is null. The length property of an array is one higher than the highest non-undefined index. For example:
var b = [];
b[10] = null;
b.length
> 11
However the only index which will not return undefined is b[10].
You can read more about it here:
http://msdn.microsoft.com/en-us/library/d8ez24f2(v=vs.94).aspx
I am not sure what you mean by uninitialized - I think that concept is captured by undefined in JavaScript.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Are Javascript arrays sparse?
I am learning JavaScript at the moment and have been reading some simple introductions and tutorials. While looking at the Array object I stumbled upon some details, which strike me as very odd, coming from other languages like C/Java/Scala/...
So lets assume we define an array as such:
var arr = ['foo','bar','qux']
We now assign
arr[5] = 'baz'
which results in our array looking like this:
arr
>> ["foo", "bar", "qux", undefined, undefined, "baz"]
And the length is as expected
arr.length
>> 6
JavaScript has kindly expanded our array to the needed length - six - and the new items are set to undefined - except for the one we actually assigned a value to.
From a low level point of view this is horrible memory-wise. Typically an array would be a continuous range in memory - making an array bigger generally involves copying the whole array to a new memory location, sufficient in size. This is a very costly operation.
Now, I do realize that this is likely not what JavaScript engines are doing, as copying around arrays would be crazy expensive and the memory space would be wasted on all these 'undefined' values.
Can someone tell me what actually happens behind the door?
Are arrays actually some sort linked lists?
Are the 'undefined' array items actually there?
How expensive is it to work with large arrays that are mostly filled with 'undefined'?
In the first version of JavaScript, there were no arrays. They were later introduced as a sub-class of that "mother of all objects": Object. You can test this quite easily by doing this:
var foo = [1,2,3,4];
for (var n in foo)
{//check if n is equal (value and type) to itself, coerced to a number
console.log(n === +(n) ? 'Number' : 'String');
}
This will log String, time and time again. Internally, all numeric keys are converted to strings. The Length property merely fetches the highest index, and adds 1 to it. Nothing more. When you display your array, the object is iterated, and for each key, the same rules apply as for any object: first the instance is scanned, then the prototype(s)... so if we alter our code a bit:
var foo = [1,2,3,4];
foo[9] = 5;
for (var n in foo)
{
if (foo.hasOwnProperty(n))
{//check if current key is an array property
console.log(n === +(n) ? 'Number' : 'String');
}
}
You'll notice the array only has 5 own properties, the undefined keys 4-8 are undefined, because there was no corresponding value found within the instance, nor in any of the underlying prototypes. In short: Arrays aren't really arrays, but objects that behave similarly.
As Tim remarked, you can have an array instance with an undefined property that does exist within that object:
var foo = [1,2,undefined,3];
console.log(foo[2] === undefined);//true
console.log(foo[99] === undefined);//true
But again, there is a difference:
console.log((foo.hasOwnProperty('2') && foo[2] === undefined));//true
console.log((foo.hasOwnProperty('99') && foo[99] === undefined));//false
RECAP, your three main questions:
Arrays are objects, that allow you to reference their properties with numeric instances
The undefined values are not there, they're merely the default return value when JS scans an object and the prototypes and can't find what you're looking for: "Sorry, what you ask me is undefined in my book." is what it says.
Working with largely undefined arrays doesn't affect the size of the object itself, but accessing an undefined key might be very, very marginally slower, because the prototypes have to be scanned, too.
Update:
Just quoting the Ecma std:
15.4 Array Objects
Array objects give special treatment to a certain class of property names. A property name P (in the form of a
String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to
2^32
1. A property whose property name is an array index is also called an element. Every Array object has a
length property whose value is always a nonnegative integer less than 2^32. The value of the length
property is numerically greater than the name of every property whose name is an array index; whenever a
property of an Array object is created or changed, other properties are adjusted as necessary to maintain this
invariant. Specifically, whenever a property is added whose name is an array index, the length property is
changed, if necessary, to be one more than the numeric value of that array index; and whenever the length
property is changed, every property whose name is an array index whose value is not smaller than the new
length is automatically deleted. This constraint applies only to own properties of an Array object and is
unaffected by length or array index properties that may be inherited from its prototypes.
An object, O, is said to be sparse if the following algorithm returns true:
1. Let len be the result of calling the [[Get]] internal method of O with argument "length".
2. For each integer i in the range 0≤i
a. Let elem be the result of calling the [[GetOwnProperty]] internal method of O with argument
ToString(i).
b. If elem is undefined, return true.
3. Return false.
Arrays are just an ordered list of objects. In JavaScript everything is an object, so arrays are not really arrays as we know them :)
You can find little internals here.
For your doubts about working with large arrays... Well, remember that the less calculation you make "client-side", the faster will be your page.
Answers:
An array in JavaScript is just the same as an object (i.e. an unordered collection of properties) with a magic length property and extra prototype methods (push() etc.)
No, the undefined items are not there. JavaScript has an in operator that test for the existence of a property that you can use to prove this. So for the following array: var arr = ['foo']; arr[2] = 'bar';, 2 in arr returns true and 1 in arr returns false.
A sparse array should take up no more memory than a dense array whose length is the number of properties actually defined in your sparse array. It will only be more expensive to work with a sparse array when you iterate over its undefined properties.
Most javascript implementations implement arrays as some flavor of binary tree or hash table with the array index as the key, so a large range of undefined objects does not use up any memory.
I was told that the arrays come in 2 parts, [value, pointer]. So the pointer of arr[2] is null. When you add a 5 it changes the address from null to point to number 3, which points to number 4, which points to number 5, which is null (so end of array).
Im not sure how true this is as ive never actually checked it. But it seems to make sense.
So you cant do the maths like on a c type array (ie to get to value 4 just do starting memory point + 4x (object amount in memory)) but you can do it by following the array peice by peice