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.
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
The Array.prototype.map function works as expected when applied on an array with undefined values:
const array = [undefined, undefined, undefined];
console.log(array.map(x => 'x')); // prints ["x", "x", "x"]
However, when using map on a sparse array with empty slots, it does not map them to 'x' as in the previous example. Instead, it returns undefined values:
const array = [,,,];
console.log(array.map(x => 'x')); // prints [undefined, undefined, undefined]
Even if we have an array with a mix of empty slots and actual values, only the latter ones are mapped:
const array = [,'a',,'b',];
console.log(array.map(x => 'x')); // prints [undefined, "x", undefined, "x"]
In contrast, I noticed Array.prototype.join works on empty slots:
const array = [,,,,];
console.log(array.join('x')); // prints "xxx"
Why does join treat empty slots as valid elements, but map does not?
Furthermore, in the join documentation, they mention that if an element is undefined, null or an empty array [], it is converted to an empty string. They do not mention empty slots, but it seems they are also converting them to an empty string.
Is it then a problem in the MDN documentation? And why not having join also ignore empty slots in the same way map does? It seems to be either a problem in the documentation or in the implementation of join.
If you're here NOT for excerpts from tutorials, but for practical ways to get array methods behave in desired way, consider the following:
const array = [,,,];
console.log([...array].map(x => 'x'));
...if you need resulting array of initial size, or
const array = [,'a',,'b',]
console.log([...array].filter(Boolean).map(x => x+'x'));
...if you need to skip empty slots
join attempts to produce a serialized representation of the array. map produces a projection of the elements of an array through some transforming function.
With map, it is possible to say: "As you step through the array, if you encounter an index that has no property, leave that property similarly unset in the output array." For all existing properties, output indices will still correspond to their input indices, and the missing properties are skipped in both the input and output.
With join's string output, we can't really do this. If we join [,'a',,'b',], an output of ,a,,b, is the best way to represent this. An output that skips missing properties -- i.e., a,b -- would be hugely misleading, appearing to be a length-2 array with elements at indices 0 and 1.
Unlike map, which can produce an array with variously present or absent properties, join is stuck rendering a string output, which cannot readily distinguish missing vs. empty properties in its output without hugely misleading results.
For completeness, here are the actual ECMAScript-specified behaviors where the function loops through the input array (in each, k is the loop variable):
Array.prototype.join
Repeat, while k < len
If k > 0, set R to the string-concatenation of R and sep.
Let element be ? Get(O, ! ToString(k)).
If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).
Set R to the string-concatenation of R and next.
Increase k by 1.
Array.prototype.map
Repeat, while k < len
Let Pk be ! ToString(k).
Let kPresent be ? HasProperty(O, Pk).
If kPresent is true, then
Let kValue be ? Get(O, Pk).
Let mappedValue be ? Call(callbackfn, T, « kValue, k, O »).
Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
Increase k by 1.
Even if you don't know how to read all of this, it's plain to see that map includes a HasProperty check in the second loop step. join explicitly says "If element is undefined or null, let next be the empty String." Get(O, ! ToString(k)) is a usual property lookup which, for ordinary objects, yields undefined when a property is absent, so the "If element is undefined" case applies.
It's worth noting that the MDN documentation simplifies its information in order to focus on the most common cases instead of adhering to rigorous completeness. (I would say that sparse arrays are an uncommon case.) In particular, they say that an empty array will serialize to the empty string, which is true. This is true in general for any value that has a toString function which returns an empty string:
["foo", { toString: a=>""}, "bar"].join()
This will produce the output foo,,bar.
This is how JS handles sparse arrays. Consider:
> let a = [,,,,,]
> a
[ <5 empty items> ]
> let b = [undefined,undefined,undefined,undefined,undefined]
> b
[ undefined, undefined, undefined, undefined, undefined ]
> let c = [,,'x',,]
> c
[ <2 empty items>, 'x', <1 empty item> ]
Empty items do not actually take up any space (beyond some overhead).
I want to initialize an array of a given size with elements set to undefined by default.
new Array(5) returns an array of 5 empty items.
Array.apply(null, Array(5)) returns exactly what I want: a five element array with each element as undefined.
Why does the second option produce a different array value than the first?
This distinction is important because I can map over an array of empty items but I can for undefined elements.
new Array(5).map(Boolean) // [false, false, false, false, false]
Array.apply(null, Array(5)) // [ <5 empty items>]
.map is a native Array object method that is expected to return a value for every iteration over the array to create a new copied array from the array. Boolean can be used as a constructor function to create a boolean object with a true or false value. Try the same thing with Number like:
new Array(5).map(Number)
In your second example, .apply calls a function with a supplied context value as the first parameter, and the parameter the function takes in as the second parameter. Also here, Array can be used as a constructor function, to construct a new Array (just like with Number and Boolean of their respective types)
First of All, Javascript array is a Object. If you check this, you will understand it. ONly specialty is,it has length property that regular object in javascript.
var array = new Array(5)
typeof array
And other thing is Array.apply mean, it will call array native function with setting given context which is null for your case and parameters are in another array.
Array.apply(null, [,,,,,]);
output will be [undefined, undefined, undefined, undefined, undefined]
I personally would use Array.from
console.log(Array.from({ length: 5 }))
// if you want to initialise the values you can add a constructor function
console.log(Array.from({ length: 5 }, (_, index) => index + 1))
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
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.