I saw code like this:
let a = 5;
[...Array(a).keys()].map(x => console.log(x));
I really want to know how all these things work. I understood that it's mapping over an array and showing output in the console. But I'm confused about what's happening inside this []. How rest operator is working and then finding the corresponding key?
Thanks in advance.
... This is not the rest operator, this is spread operator (in this situation).
The Array(5) creates an empty array with length 5.
[...Array(5)] creates this [undefined, undefined, undefined, undefined, undefined].
At last [...Array(5).keys()] creates
[0, 1, 2, 3, 4]
Because the keys of any array in JavaScript (except if the keys have been explicitly modified) are the numbers 0, 1, 2....
Related
I found this code online and it works; however, I can't work out how!
Can anyone please explain how this code works?
const arr_seq = Array.apply(null, {
length: 10
}).map(Number.call, Number);
console.log(arr_seq)
Output is:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Also, can anyone explain why this code, which I thought might do exactly the same thing, instead creates an array filled with undefined?
const arr_undef = Array(n).map(Number.call, Number);
Array.apply expects an array(-like) value for its second argument. It will then create an argument for each slot in this array-like object.
Since this code passes { length: 5 } as argument, the apply method will call Array with 5 values. But when reading { length: 5 }[i] for i in the range 0..4, it will always get undefined. So Array.apply(null, { length: 5 }) will translate to Array(undefined, undefined, undefined, undefined, undefined).
Then the .map call has a second argument, which is the thisArg argument. This ensures that not just call is called, but Number.call, i.e. with the right this setting. With Number.call the first argument will receive those undefined values (explained in the previous paragraph) and as second argument, the index of the mapping. So we get these calls:
Number.call(undefined, 0)
Number.call(undefined, 1)
Number.call(undefined, 2)
Number.call(undefined, 3)
Number.call(undefined, 4)
This gives the same result as:
Number(0)
Number(1)
Number(2)
Number(3)
Number(4)
And .map will return an array with those values.
Newer ways to do this
Since ECMAScript 2015, JavaScript has Array.keys() and spread syntax for array literals. So we can now achieve the same in a less cryptic way:
console.log([...Array(5).keys()]);
Array(n) creates an empty array of length 10; the map/Number.call part requires something to be there to get the index:
const arr = Array(5).fill(null).map(Number.call, Number);
console.log(arr);
Could someone explain me why this statement generates a new array of null values instead a array of 1's?
let a = [...Array(3).map(_ => 1)];
console.log(a);
results [null, null, null] instead of [1,1,1]
But, the following code...
let b = [...Array(3)].map(_ => 1)
console.log(b);
results [1,1,1]
Some might consider this a dup of the post I referred to in the comments. The gist is that the array constructor creates an array of undefined references, and the spread operator creates an array of references to undefined.
map skips undefined references, but maps happily over refs to undefined. See it with a console.log() side effect.
new Array(3).map(el => console.log('this code is never reached'))
Compared this to
[...new Array(3)].map(el => console.log(el))
When you creating empty array that have a length, all slots are unassigned, they don't even have undefined value. Map on
the other hand, can only iterate thru assigned values.
Good explanation is on developer.mozilla.org
MDN Array()->Parameters->arrayLength
(...) Note: this implies an array of arrayLength empty slots, not slots with actual undefined values (...)
MDN map()->description
(...) callback is invoked only for indexes of the array which have assigned values (...)
This is why on Array(3).map(_=>1), map() callback have no effect.
Second part is how spread is working. When you are spreading array with 3 empty slots, you are creating new array, that have
filled slots. Now you have all slots with value undefined. From now, map will work as expected.
Examples:
Welcome to Node.js v12.20.1.
Type ".help" for more information.
> Array(3)
[ <3 empty items> ]
> [...Array(3)]
[ undefined, undefined, undefined ]
> const a = []
> a.length = 2
> a
[ <2 empty items> ]
> [...a]
[ undefined, undefined ]
Defining arrays in javascript using Array(n) defines an empty array, please correct me if I am wrong. Now if I write the below code:
Array(2).map((val) => console.log(val))
.. nothing happens, but when I spread out the elements of the array into an other array, like so:
[...Array(2)].map((val) => console.log(val))
.. I get undefined console logs. Can anyone please help me understand what is happening here ?
Without spreading, the array is simply an object with a length property, with no items populated:
{ length: 2, proto: Array(0) }
When you spread (...) into an array, if the object you're spreading has a length property, it will spread that many items, starting from obj[0], obj[1], etc. The object in question doesn't actually need to have those numeric properties - if it doesn't, then the result of the spreading will be an array of undefineds:
console.log([...Array(2)]);
An array of undefineds can be iterated over - here, the indicies 0 and 1 do exist in the array object, they just happen to not be assigned to anything.
const arr = [undefined, undefined];
console.log(1 in arr);
So, in short, it's because the difference between an array such as
{ length: 2 } // no elements to iterate over
and
{ length: 2, 0: undefined, 1: undefined }
Note that you shouldn't use map if you're not using the transformed array that .map returns - otherwise, if you just want to iterate over each item and console.log it, for example, you should use forEach.
This is because the spread operator takes the iterable's length, then iterates over the passed value (See spec).
Since all returned valued from iterating over Array(2) are undefined, your second line maps over [undefined, undefined], while your fist line maps over [empty x 2]. Note that "empty" aren't actual values.
map doesn't iterate over "empty" entries, like those created by Array(2), but does iterate over undefined values:
Because Array(2) doesn't populate the array. But it does set the length property to 2. So in the first instance, map has nothing to iterate over. But the destructuring creates an array of 2 elements of undefined, which allows map to work since undefined is a valid primitive.
This is how Array.map works - please read here: Array.prototype.map()
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).
This question already has answers here:
What is the difference between [undefined] and [,]? [duplicate]
(4 answers)
Closed 6 years ago.
I was just fiddling with some JS and found something I thought would not happen.
var arr = [,];
arr.length // 1
arr[0] // undefined
Moreover, [,,] has a length of 2. Shouldn't these arrays have lengths of 0? I thought commas were only used to separate elements in an array, so why does it appear that commas are counted as elements in these particular arrays? Does this kind of behavior with arrays have any sort of application?
In an array literal, a comma at the end is ignored, but all the other commas delimit elements. If you omit the value of an element, it defaults to undefined, but the element is still there. So
[,]
is equivalent to
[undefined,]
which is equivalent to
[undefined]
This has 1 element whose value is undefined.
Similarly
[,,] = [undefined, undefined, ] = [undefined, undefined]
which has 2 elements.
The default element behavior is more useful when you want to omit elements in the middle of an array.
[1, 2, 3, , , 6, 7, 8]
Actually, there's a small difference between the two ways of creating undefined elements. In Javascript, an array is actually an object that has a length property and properties whose names are the indexes of the array elements. Normally, if an array has length = N, the index properties will be all the integers from 0 to N-1. But when you omit an element in the array literal, no property is created for that index.
For most purposes, this missing property is not significant, because accessing a nonexistent property of an object returns undefined, just as if you have a property whose value is undefined. You can only detect these missing properties by using a method like hasOwnProperty or calling Object.keys. The Javascript console uses something like this to display gaps in the array differently from explicit undefined elements.
So
Object.getOwnPropertyNames([1, 2, 3]) => ["0", "1", "2", "length"]
Object.getOwnPropertyNames([1, , 3, 4]) => ["0", "2", "3", "length"]
Object.getOwnPropertyNames([, , , , ]) => ["length"]
The reason for ignoring the last comma is so you can write:
[
"foo",
"bar",
"baz",
"quux",
]
This makes editing easier, because you can insert and delete lines without having to special-case the last element.
That's called elision. It creates a "hole" in the array (a sparse array). There are probably applications I don't know about and have forgotten about, the only one I can think of right now is the one I show here: https://stackoverflow.com/a/29629588/1034448.
[,...Array(10)].map((x, i) => { /* range of `i` is 1 - 10 */ })
[,,] is not equivalent to [undefined, undefined,]. There is a big difference: iteration methods will not include elements at the elided indexes. Example:
var enumerated = [];
function callback (x, i) {
enumerated.push(i);
}
[,,].forEach(callback);
console.log(enumerated); // logs []
[undefined, undefined,].forEach(callback);
console.log(enumerated); // logs [1, 2]
Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Array_literals
The trailing comma is ignored in newer browsers.
Why does invoking Array.prototype.map directly on Array instance o result in an "unmodified" array?
var o = Array(3); // [ undefined, undefined, undefined ]
o.map((x,y) => y*2); // [ undefined, undefined, undefined ]
Instead, I have to use apply (or call):
Array.apply(0, o).map((x, y) => y*2)); // [ 0, 2, 4 ]
What am I missing?
Finally, an alternative to the above is:
[...o].map((x, y) => y*2); // [ 0, 2, 4]
I presume because this corrects whatever is missing in my original implementation.
Why does invoking Array.prototype.map directly on Array instance o result in an "unmodified" array?
Because .map only works on elements that actually exist. Array(3) creates an empty array of length 3. Put differently: .map omites holes.
Instead, I have to use apply (or call): ... What am I missing?
Array.apply(0, o) is equivalent to Array(undefined, undefined, undefined) in your case, i.e. you are creating an array that contains three elements.
The different becomes more apparent if you compare
console.dir(Array(3));
// vs
console.dir(Array.apply(null, Array(3)));
The first one only has property length, the second one also has properties 0, 1 and 2.
Finally, an alternative to the above is: ...
The spread operator will call o[Symbol.iterator]. The iterator of an array will iterate over holes, just like you would when using a normal for loop.