Related
In one app I saw this part of code:
const arrayCreate = length => [...Array(length)];
It works, but I'm not sure how. We have a arrow function that takes a length parameter, and creates an array in array or what? :)
And what does the spread operator doing here?
Array(length) will create an array of length empty slots, for which, though, you cannot map or iterate using array methods like .map .forEach etc.
When you spread (...) the array with the empty slots it will create a new array with undefined for each array position. So you can now .map and .forEach because they are not empty slots.
So this is a way to create an array of length filled with undefined.
You could do the same with
Array(length).fill()
or
Array.from(Array(length))
#Gabrielse's post answers the question pretty well. However, there's just one more thing that I'd like to add to it.
One use-case where you might do something like this is when you have a set iterable and you want its elements inside an array. for e.g.
const set = new Set();
set.add('foo');
set.add('bar');
set.add('baz');
Suppose, you've the items foo, bar, baz in a set. A simple console.log(set); would result Set { 'foo', 'bar', 'baz' }. The result you get of course is a set. But what if you want these items in an array?
A simple way to do this would be to use ...(spread operator) applied to the set inside an array. The result of console.log([...set]); would be [ 'foo', 'bar', 'baz' ].
Interesting! The developer who made this probably wanted to optimize the performance at writing a first value at index or to be able to iterate through the empty values
An array is a javascript object that have:
key/value pairs assigned to respectively array's index/ value at index
length (size of the array)
prototype (getters, setters, array methods...)
Array(length) or new Array(length) returns { length: 0, prototype: {} }. The object is empty, with the length equals to passed in length
Now [...Array(length)] will return { 0: undefined, 1: undefined, 2: undefined: 3: undefined, 4: undefined, 5: undefined, length: 5, prototype: {} }. The properties are now allocated to their undefined values
I'm solving a problem where the task is to merge multiple objects from the input array and return a single object with all the keys and values merged. In case a key exists in one or more objects in the input array, the most recent value for that key should be stored in the final returned object.
An example -
var a={1:'1',2:'2',3:'3'},
b={3:'4',5:'6',6:'7',7:'8'},
c={5:'9',8:'9',6:'12',23:'35'}
o=[a,b,c];
The returned object should be -
{ '1': '1','2': '2','3': '4','5': '9','6': '12','7': '8','8': '9','23':'35' }
As an example of the duplicate key case, key 3 exists in both objects a and b, so in the final result the value is {3:'4'} because that is the most recent.
I want to use the spread syntax for Objects in this problem, but that solves only a part of it. What I mean is, if I use spread for each object individually, that works, like -
function foo() {
var a={'1':'1','2':'2','3':'3'},
b={'3':'4','5':'6','6':'7','7':'8'},
c={'5':'9','8':'9','6':'12','23':'35'},
arr = [a,b,c];
return {...arr[0], ...arr[1], ...arr[2]}
}
console.log(foo());
For this to work, I need to write out each array element with spread, as in the above snippet - ...arr[0], ...arr[1], ...arr[2]. However, the input array can contain any number of objects, so writing out each element is not feasible.
Normally, using spread on an iterable like an array, allows you to expand the array elements, like so-
var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];
console.log(lyrics)
Is it possible to use spread on the input array to collect all the individual objects, on which spread can be applied again, to get the final object?
You could just use Object.assign():
return Object.assign(...arr);
Here's a complete snippet:
function foo() {
var a={'1':'1','2':'2','3':'3'},
b={'3':'4','5':'6','6':'7','7':'8'},
c={'5':'9','8':'9','6':'12','23':'35'},
arr = [a,b,c];
return Object.assign(...arr);
}
console.log(foo());
Note that this implicitly modifies the first object in your array. If you don't want that, pass a new empty object as the first argument to Object.assign():
return Object.assign({}, ...arr);
You can directly use spread syntax on object to merge them.
function foo() {
var a={'1':'1','2':'2','3':'3'},
b={'3':'4','5':'6','6':'7','7':'8'},
c={'5':'9','8':'9','6':'12','23':'35'};
return {...a, ...b, ...c};
}
console.log(foo());
If you have an array, then you can use array#reduce and Object#assign.
var a={'1':'1','2':'2','3':'3'},
b={'3':'4','5':'6','6':'7','7':'8'},
c={'5':'9','8':'9','6':'12','23':'35'},
arr = [a,b,c];
var result = arr.reduce(function(r,o){
return Object.assign(r,o);
},Object.create(null));
console.log(result);
NOTE : The Rest/Spread Properties for ECMAScript proposal (stage 3) adds spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.
I have declared an array a = [1,2,3,4,5]
When I write a[1] it returns 2 which is perfectly fine but when I write a['1'] it also gives me 2 instead of giving an error.
I was expecting an error there. Why does it behave like that??
All property names are strings.
If you pass a number, it gets converted to a string before being used to look up the property value.
console.log(1 == '1');
In JS an Array is basically an Object and thus mostly behaves like one. In this case, these are equivalent as long as you dont access Array's .length or try iterating a:
const a = {"0": foo};
const b = ["foo"];
Also this would work:
const a = ["foo"];
a.bar = "baz";
console.log(a);
So that a[1] and a['1'] are equivalent is exactly what's to be expected.
First of all, array is also object having property names as 0,1,2,....n
Property names must be strings. This means that non-string objects cannot be used as keys in the object. Any non-string object, including a number, is typecasted into a string via the toString method. [Ref]
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