I recently faced issues where I couldn't use .map on iterable objects and had to convert them to arrays with either Object.values or Array.from
I seem to get same result in both cases, so I'm wondering what the difference is. In which case do you use one over the other?
It's not true in the general case that you'll get the same result from Array.from(x) and Object.values(x) when x is an iterable object. In fact, it's fairly rare (just arrays and array-likes¹ that hide their length property by making it non-enumerable or inheriting it and don't have any other own, enumerable properties). Array.from will loop through the iterable object using iteration; Object.values will loop through the object looking for own, enumerable properties. For an array that doesn't have any non-array properties, that ends up being the same thing, but it's not true in the general case.
In the general case, Array.from and Object.values do very different things and have different results:
Array.from creates an array from any iterable or array-like object (an object with a length and index properties like 0 and 1).
Object.values creates an array of the values of an object's own enumerable properties. The object doesn't have to be iterable (Object.values doesn't even look to see if it is.)
Array.from accepts a second argument, a function that can be used to map the values from the iterable/array-like into new values for the array.
Here's an example where Object.values gets [] (because the Set has no own enumerable properties) but Array.from gets [1, 2, 3] (because the Set's iterator provides those three values):
const x = new Set([1, 2, 3]);
// Shows an empty array
console.dir(Object.values(x));
// Shows [1, 2, 3]
console.dir(Array.from(x));
Conversely, here's one where Object.values returns [1, 2, 3] because the object has those as own enumerable properties, but Array.from returns [] because the object is neither iterable nor array-like:
const x = {a: 1, b: 2, c: 3};
// Shows [1, 2, 3];
console.dir(Object.values(x));
// Shows []
console.dir(Array.from(x));
¹ An array-like object is an object with:
A length property
Properties named with strings that are numbers in the normal decimal form for the values 0 through length - 1 (but there can be gaps).
For instance, this is an array-like object:
const obj = {0: "zero", 1: "one", length: 2};
(I've used number literals for the property names, but they'll be converted to string.)
Note that that object will give you different results for Array.from and Object.values because length is an own, inherited property.
Array.from creates a new array, usually from another array, but can also be used on Set. It also has a second arguments, that allows to map each element to another value.
Object.values on the other hand creates a new array with the values from the supplied object.
I'm not exactly sure why you say they do the same thing, as they don't. Except maybe the fac that you end up with an array as the results. Take this example for instance:
Object.values({a: 2, b:3}) // will return [2, 3]
on the other hand
Array.from({a: 2, b:3}) // will return [], as an object is not an array-like object
Related
let arr = new Array(3)
console.log(Object.keys(arr)) // []
console.log([...arr.keys()]) // [0,1,2]
Why is there a difference? It seems that an array in javascript can simultaneously have a length of 3 and be empty. How do you know the behavior of the different functions that operate on sparse lists?
Object.keys() does expect the parameter to be of type object and will cast anything else implicitly to one. Passing an empty array will be casted to an empty object I guess: Object.assign({}, new Array(3)) will give {}
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
if we have: let myArr = [1, 2, 3, 4] , and if we do: myArr.values(); we will get Array Iterator [1, 2, 3, 4].
So, what's the difference of an array and an array Iterator?
They have different purposes and functionalities:
arrays are rooms, iterator is the room guards, allowing to move to next rooms
Iterator allows you to traverse, e.g. next() can be called
Iterator does not allow you to access via index, e.g. iterator[0]
const x = [1, 2, 3];
const iterator = x.values();
console.log(x[1]); //OK -> 2
console.log(iterator[1]); // -> Undefined
console.log(iterator.next()); //OK -> 1
console.log(iterator.next()); //OK -> 2
console.log(iterator.next()); //OK -> 3
console.log(iterator.next()); //OK -> { value: undefined, done: true }
//x.next(); //✗Error: x.next is not a function
When you declare an array as let myArr = [1, 2, 3, 4], it returns you an object of type Array. It has all the properties and methods associated with the Array type, like length, push, pop, etc.
On the other hand, myArr.values() returns an iterator. The iterator is not the same as the original Array in the sense that it does not have the properties and methods of an Array. Instead, being an iterator, it has its own methods like next, etc that you can use to iterate over the values.
Look here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
In JavaScript an iterator is an object which defines a sequence and
potentially a return value upon its termination. More specifically an
iterator is any object which implements the Iterator.
Also good explain is here
https://www.geeksforgeeks.org/javascript-array-values/
By default, using arr[Symbol.iterator] will return the values()
function.
reference
An array is not the same thing as an iterator, though they're somewhat similar.
An array is a collection of data that you can call methods on (like forEach and map), and that you can pass to other array methods (like concat). Arrays also happen to be iterable (they have a Symbol.iterator method).
On the other hand, an iterator, or an array iterator, only provides functionality for iterating over the data; it doesn't allow for anything else. You cannot push to an iterator, or modify it, or extract values from certain indicies (without actually iterating over it). All you can do with an iterator is iterate, whereas with the full array, you can do many other things.
Here, there's rarely a use case for invoking Array.prototype.values, because the iterator it returns is identical to the iterator that would be used if you had iterated over the plain array (with for..of). Eg, if you have
let myArr = [1, 2, 3, 4];
const iterator = myArr.values();
for (const value of iterator) {
console.log(value);
}
you may as well just iterate over the array directly:
let myArr = [1, 2, 3, 4];
for (const value of myArr) {
console.log(value);
}
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.