Please have a look at the below script. I am testing it with Chrome.
/*declare a new set*/
var items = new Set()
/*add an array by declaring as array type*/
var arr = [1,2,3,4];
items.add(arr);
/*print items*/
console.log(items); // Set {[1, 2, 3, 4]}
/*add an array directly as argument*/
items.add([5,6,7,8]);
/*print items*/
console.log(items); // Set {[1, 2, 3, 4], [5, 6, 7, 8]}
/*print type of items stored in Set*/
for (let item of items) console.log(typeof item); //object, object
/*check if item has array we declared as array type*/
console.log(items.has(arr)); // true
/*Now, check if item has array we added through arguments*/
console.log(items.has([5,6,7,8])); //false
/*Now, add same array again via argument*/
items.add([1,2,3,4]);
/*Set has duplicate items*/
console.log(items); // Set {[1, 2, 3, 4], [5, 6, 7, 8], [1, 2, 3, 4]}
Why it is returning false at items.has([5,6,7,8])?
Why it is allowing duplicate values? I thought "A set is in an ordered list of values that cannot contain duplicates"
How to access array added by items.add([5,6,7,8])?
Why it is returning false at items.has([5,6,7,8])?
From MDN
The Set object lets you store unique values of any type, whether primitive values or object references.
The objects are compared using the reference, not the value. Sets uses SameValueZero(x, y) comparison algorithm to compare values. It says Return true if x and y are the same Object value. Otherwise, return false.
Why it is allowing duplicate values? I thought "A set is in an ordered list of values that cannot contain duplicates"
Same as #1. An non-primitive value is said to be already exists in set if the same object(not just same looking) already added in the set.
How to access array added by items.add([5,6,7,8])?
You've to create a variable and add the variable to the set. Then this variable can be used to check if set has that array or not.
Quoting the specification:
Set objects are collections of ECMAScript language values. A distinct value may only occur once as an element of a Set’s collection. Distinct values are discriminated using the SameValueZero comparison algorithm.
(emphasis mine)
The SameValueZero comparison algorithm handles any two arguments of the same type (where that type isn't null, undefined, a string, number, boolean, or Symbol) as follows:
Return true if x and y are the same Object value. Otherwise, return false.
Ultimately, what this means is that the objects are compared by reference (that is, effectively, "do these two objects point to the same location in memory?") Each time you create an array using either brackets ([]) or constructor invocation (new Array()) you create a new instance of Array in memory. When you keep a reference to the array, it will match (e. g. arr === arr). When you create new instances and compare them they will not match, even though their contents would compare as equal (e. g. [1, 2, 3] !== [1, 2, 3]).
Array comparision does not compare values it compares references so it is returning false. [1] === [1] will return false always.
See reference MDN
The Set object lets you store unique values of any type, whether primitive values or object references.
you are passing new object not reference so it is allowing to add duplicate. which is actually visually similar but reference are different.
Pass reference by assigning it to variable to access the added array.
Related
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
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
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
In JavaScript, how do I mutate the value of an array inside of a function? I know that happens when using certain array methods, but it doesn't seem to work for normal assignment.
var arr = [4]
function changeArray(arr) {
arr = [1,2,3];
}
// arr is still equal to [4]. I want it to equal [1,2,3].
It's my understanding that this code doesn't change the value of "arr" outside of the function. How can I do it in a way that mutates the array that was passed as an argument?
You can use .splice:
arr.splice(0, arr.length, 1, 2, 3);
Or you can empty the array and .push the values:
arr.length = 0;
arr.push(1, 2, 3);
// or given an array of values
arr.push.apply(arr, newValues);
However, make sure it is clear that the function changes the array in place. While there are native functions that does this (like Array#sort), I'd argue that this is not as common as simply returning a new array.
It's my understanding that this code doesn't change the value of "arr" outside of the function.
Correct. Assigning a new value to a variable or property never changes the value of another variable or property (exceptions: global scope and with statements).
You basically tried to change the value of a variable, not mutate the value itself.
JavaScript is call/pass/assign by value (Wikipedia also mentions "by sharing"), not by reference.
Any programming language which provides arrays (lists, vectors, tuples etc.) must decide whether they have reference or value semantics, with the usual/obvious choice being reference semantics for mutable arrays and value semantics for immutable ones.
JavaScript which provides mutable arrays appears to have chosen reference semantics e.g. given
var a = [1, 2, 3]
var b = [1, 2, 3]
then a != b, as expected because though they have the same contents, they are different arrays.
However when you use them as keys in an object, the picture changes; if you set obj[a] to a value, then obj[b] gets the same value. Furthermore, this remains true if you change the contents of the arrays; at least when I tested it in Rhino, it behaves as though the interpreter were recursively comparing the full contents of the supplied and stored key arrays on every lookup, complete with a check for the infinite loop that would occur if one of the arrays were made to point to itself.
Is this the intended/specified behavior in all implementations?
Does it also apply to objects used as keys?
Is there any way to get the other behavior, i.e. to look up values using arrays as keys with reference semantics?
When arrays are used as property names they are cast to a string:
[1,2,3].toString() == '1,2,3'
Once turned into a string value, arrays with the same contents would map to the same property.
To answer your last question, you can't use objects to reference property names (keys) whereby only the same object maps to the same property (1:1 mapping).
obj[a] and obj[b] will run the toString function on the arrays and produce the same result for both. It doesn't try to use the arrays as keys.
var a = [1,2,3];
var x = {};
x[a] = "test";
var i;
for(i in x)
{
alert(i); //"1,2,3"
}
jsFiddle example