I've noticed that if I do:
Array(n).map(() => console.log('test'))
I get nothing printed.
However if I do:
Array(n).fill().map(() => console.log('test'))
I get test printed out n times.
Why is this the case? If I do Array(n).length I get back n.
I notice in the REPL that Array(5) returns:
[ , , , , ]
Whereas Array(5).fill() returns:
[ undefined, undefined, undefined, undefined, undefined ]
In both cases, typeof any element in the array === undefined.
So, what's going on?
map only operates on defined integer properties of an array. Array(n) does not set integer properties, while Array(n).fill() does. There's a difference between a property that doesn't exist and an extant property whose value is undefined.
Array(n) sets the length property of the array, but it does not set any properties. The array object does not have any integer properties.
.fill sets all of the integer properties for an array from zero up to one less than length. When you do Array(n) you set the length property of the new aray, and then .fill() defines and sets each integer property up to n-1. The array created by Array(n).fill() does have properties defined up to length - 1. (The properties happen to be set to undefined, because you didn't pass an argument to fill, but they do exist.)
In pracitcal terms, you can see the difference if you do Object.keys(Array(4)) (empty array) versus Object.keys(Array(4).fill()) (a list of strings "0" to "3"). In the first case, the properties don't exist; in the second case they do.
Array(n) creates a new array of size n, the contents have not been defined.
Array(n).fill() creates an array of size n where every element is set to whatever you passed into fill or undefined in your case since you passed nothing.
Array(n).fill('test') creates an array of size n filled with 'test'.
I had a requirement to create a scale using a minValue, maxValue, and precision. So, for example, with minValue = 5, maxValue = 7, and precision = 0.5, I should get the array [5, 5.5, 6, 6.5, 7]. I used the following (based off of answers above):
const tickCount = ((maxValue - minValue) / precision) + 1
const ticks = Array(tickCount)
.fill()
.map((x, i) => minValue + i * precision)
Related
This is from the MDN documentation:
[].copyWithin.call({length: 5, 3: 1}, 0, 3);
// {0: 1, 3: 1, length: 5}
My confused misunderstanding:
Where does the property 0:1 come from? The non-array object with 2 properties will have values from the 4th on copied into the 0th on location so noop. The output is a reversal and a new property {0:1}
First, let's remember that standard arrays are just objects with special handling of properties whose names are array indexes*, a special length property, and the Array.prototype as their prototype. Array indexes are property names (we write them as numbers, but technically, at a specification level, they're strings — naturally, implementations optimize them, though).
We can use copyWithin on a non-array object because it's "intentionally generic" — it doesn't check that it's being called on an array, so long as the object it's called on has a length property.
Your original object, {length: 5, 3: 1}, has two properties: "length" (value 5) and "3" (value 1). Then the copyWithin(obj, 0, 3) call is made, which says "Copy the entries starting with index 3 through the end of the 'array' to index 0." So very roughly:
var target = 0;
var source = 3;
while (source < obj.length) {
if (source in obj) { // If the object has this property...
obj[target] = obj[source]; // ...copy it
}
++target;
++source;
}
So it ends up copying property "3" (value 1) to property "0", then moving on to property "4", which doesn't exist and so is skipped, and then stopping because source isn't < obj.length anymore.
You may find it useful to read through MDN's polyfill for copyWithin and the spec to get a good idea what it does.
* From that link:
An integer index is a String-valued property key that is a canonical numeric String (see 7.1.16) and whose numeric value is either +0 or a positive integer ≤ 253-1. An array index is an integer index whose numeric value i is in the range +0 ≤ i < 232-1.
Your source array-like object is roughly equivalent to the following array:
[undefined, undefined, undefined, 1, undefined]
(length of 5, and 1 at index 3)
copyWithin copies what's at index 3 to index 0.
That's exactly what happens here.
From the documentation:
arr.copyWithin(target, start)
target Zero based index at which to copy the sequence to. If negative,
target will be counted from the end.
start Optional Zero based index at which to start copying elements
from. If negative, start will be counted from the end.
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.
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.
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.
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:
a length property (for iteration)
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.
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