Why can't I concat an array reference in JavaScript? - javascript

I have two arrays, one comes as a reference (parameter) from a function, and the other is created as part of the function - exactly the same scenario as described here:
Add two arrays without using the concat method
I was using the push.apply() method as per the suggestion above, but can someone please explain to me, why I can't use concat() to merge two arrays if the array is sent into the function as a reference?

Refer to Array.concat on MDN:
Any operation on the new array will have no effect on the original arrays, and vice versa.
This makes it behave differently from Array.push.apply which will mutate the original Array object - the return value of Array.concat must be used. Otherwise, it works as explained in the MDN link above.

If you use concat the original array will be unmodified. If you have a reference to it you wont see the new elements.
var arr1 = [ "a", "b" ];
var arr2 = [ "c", "d" ];
arr1.push.apply(arr1, arr2);
Basically does this:
[ "a", "b" ].push("c", "d");
apply turns an array into a list of arguments. The first argument to apply is the context by the way, arr1 in this case since you want the push to apply to arr1.
You can use concat:
var arr1 = [ "a", "b" ];
var arr2 = [ "c", "d" ];
var arr3 = arr1.concat(arr2);
This leaves the original arr1 as it was. You've created a new array that has both arr1 and arr2 elements in it. If you have a reference to the original arr1 it will be unmodified. That might be a reason to not want to use concat.

Let's say we have 2 arrays "a" and "b". Array.concat method will return new instance of Array "c" which represents concatenation between a and b without any mutation of a or b. Array.push return last index of pushed element and mutate this instance.
Since ES6 (or 15, not sure) it's possible to unpack parameters and you can use push to concatenate (without harmful code) as well
a = [1,2,3,4]; // a=[1,2,3,4];
b = [5,6,7,8]; // b=[5,6,7,8];
a.push(...b) // a=[1,2,3,4,5,6,7,8]; b=[5,6,7,8]

Related

JS Array Destructuring - Using a Binding Pattern as the Rest Property

MDN recently updated some of their docs and I came across the below piece of code. Can anyone explain what would be a practical use case of the following?
From MDN:
The rest property of array destructuring assignment can be another array or object binding pattern. This allows you to simultaneously unpack the properties and indices of arrays.
const [a, b, ...{ pop, push }] = [1, 2];
console.log(a, b); // 1 2
console.log(pop, push)
push() is called on an array, so what is the point of unpacking the push() method out of an array? I can't think of a single practical use to ever using this.
<subjective>That's ... not a good example of that feature. :-D</subjective> It works because the rest element creates a new array, and then the object destructuring pattern { pop, push } picks out those properties from that new array (not the original array).
The closest I can come to a useful example is if you want to know how many additional elements there were beyond the ones you wanted, but you don't want/need the actual array of them:
let a, b, length;
[a, b, ...{ length }] = [1, 2];
console.log(length); // 0, there were no extra elements
[a, b, ...{ length }] = [1, 2, 3];
console.log(length); // 1, there was one extra
[a, b, ...{ length }] = [1, 2, 3, 4];
console.log(length); // 2, there were two extra
...but I think the fact is that while you can use object/array destructuring on the rest element because it falls out naturally from the way destructuring patterns work, it's unlikely to be all that useful.
I should note that in the more general case, using object destructuring on an array can indeed be useful, just probably not when applied to a rest element. It's especially useful for augmented arrays like the one you get from RegExp.prototype.exec, which includes not just the array elements for the match and any capture group values, but also index (where the match occurred), input, groups, and indices. You might want those. Object destructuring on an array lets you get the specific elements you want as well as non-element properties, for example:
const str = "an example string";
const match = /example/.exec(str);
if (match) {
const {0: matched, index } = match;
console.log(`Matched ${JSON.stringify(matched)} at index ${index}.`);
}
It's also useful for picking out just a couple of elements from the middle:
const array = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
const {2: two, 7: seven} = array;
console.log(`two = ${two}, seven = ${seven}`);

A copy of an array with spread syntax modifies the copied array, how come? [duplicate]

From mdn: Spread Syntax
Note: Typically the spread operators in ES2015 goes one level deep while copying an array. Therefore, they are unsuitable for copying multidimensional arrays. It's the same case with Object.assign() and Object spread syntax. Look at the example below for a better understanding.
var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array b is: [[2], [3]]
What is the point of the above statement? The above code sample works just the same as if you'd copied the array in a to b using the .slice() method. I tried adding another dimension to the array here: https://repl.it/HKOq/2 and things still worked as expected.
So why is the spread syntax unsuitable for copying multidimensional arrays?
I'd appreciate any help.
EDIT:
Reading the answers by estus and vol7ron helped me figure things out. Basically, as estus points out technically there are just arrays inside arrays rather than multidimensional arrays.
And as vol7ron explains only the first level of the array is copied so the objects in memory remain the same for any further nested elements.
I was also wrong to suspect that using the spread syntax was supposed to behave any differently than the slice operator
Man, programmers are really poor at displaying examples that actually show the difference.
var a = [[['a', 'b'], ['c', 'd']], 'e'];
var b = [...a];
b[0][0][0] = 'z';
b[1] = 'x';
console.log('a', a);
console.log('b', b);
This outputs:
a [[["z", "b"], ["c", "d"]], "e"]
b [[["z", "b"], ["c", "d"]], "x"]
Notice something fishy? Both arrays [0][0][0] value was changed. Meaning that the object sitting at [0][0][0] in both arrays are referenced to the same object, and is not a copy. However the [1] values are different meaning that it is indeed a copy.
Shallow copy means the first level is copied, deeper levels are referenced.
Arrays are objects, and [...a] creates a shallow copy of a array object.
For the language itself there are no multidimentional arrays - there are another arrays inside an array. It doesn't matter if contains arrays, plain objects, functions or primitives. For primitives, their values will be copied. Otherwise, the references to objects will be copied. This is what
It's the same case with Object.assign() and Object spread operators
part refers to.
And regarding
The above code sample works just the same as if you'd copied the array in a to b using the .slice() method
...it truly does. This is a neater way to write a.slice() or [].concat(a). With a considerable exception. ES6 rest operator (as well as Array.from(a)) works equally for all iterables, not just for arrays.
For a deep copy of an object ES6 offers nothing new, an object (which an array is) should be recursively copied by hand. To address all the concerns it still makes sense to use proven third-party helper functions, such as Lodash cloneDeep.
New arrays are not created for internal array elements (for multi-dimensional array):
// One-dimensional array
var a = [1,2,3];
var b = [...a];
a[0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0] == 1
// got: b[0] == 1
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = [...a];
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 'a'
It works like slice(), so you would have to traverse the array and create new arrays for each dimension. Here's one quick example:
// Multi-dimensional array
var a = [[1], [2], [3]];
var b = (function fn(ar){
return ar.map(el=>Array.isArray(el)&&fn(el)||el)
})(a);
a[0][0]='a';
console.log('a',a);
console.log('b',b);
// expected: b[0][0] == 1
// got: b[0][0] == 1
So what the example is trying to convey is that var b = [...a]; will not unroll the inner arrays of a (e.g b = [1,2,3]), but instead, b will be [[1],[2],[3]]. So b.shift() removes and returns the first element of b which is [1], then the second shift() just removes 1 from that returned array. In one word ... only reaches one level down into your spreaded array, e.g. var b =[...a] is equivelent to var b = [a[0], a[1], a[2]], not var b = [ a[0][0], a[1][0], a[2][0] ] in the example

Merging 2 arrays with different value types

Developing in MEAN stack. Express received and process req array parameters (initially string) and Mongoose returns object array property. I am facing very strange problem. Console.log outputs arrays as follows:
var arr1 = [ '5acde7adf7d2520e3b205970', '5acde7c0f7d2520e3b205971' ];
var arr2 = ["5acde7adf7d2520e3b205970","5acde7c0f7d2520e3b205971"];
first array is a JSON.parsed variable, second the property of MangoDB returned array property.
I need to compare arrays and if they different - perform operation.
Lodash isEqual function is always false.
Lodash union the same as concat and filter, output arrays as follows (log output):
[ 5acde7adf7d2520e3b205970,
5acde7c0f7d2520e3b205971,
'5acde7adf7d2520e3b205970',
'5acde7c0f7d2520e3b205971' ]
If I check types of each array value then the first array values are objects the second are strings...
The only way I can property merge arrays is by preprocessing them like: JSON.parse(JSON.stringify(arr1)). Then all values are strings and merged properly as they shall:
[ '5acde7adf7d2520e3b205970', '5acde7c0f7d2520e3b205971' ]
Anybody faced this problem? Probably have some better ideas how to handle it
The best solution I found, for my problem, is to use map function to even array values. Ex:
arr2.map(String)
If it's always going to be an array of primitives, it should be quite easy to just compare each of their values like so:
const arr1 = [ '5acde7adf7d2520e3b205970', '5acde7c0f7d2520e3b205971' ];
const arr2 = ["5acde7adf7d2520e3b205970","5acde7c0f7d2520e3b205971"];
const isSame = (arr1, arr2) => {
if (arr1.length !== arr2.length) return false;
return arr1.every((arr1elm, i) => arr1elm === arr2[i]);
}
console.log(isSame(arr1, arr2));
The fact that one array may have been defined with double quotes and one with single quotes shouldn't affect anything, since they're already deserialized - it's still an array of strings underneath, not a string itself.

How do I save a copy of an array before sorting? [duplicate]

This question already has answers here:
Copy array by value
(39 answers)
Closed 9 years ago.
I'm trying to save a copy of the array before I sort it:
words_Array = ["Why", "doesnt", "this", "work?"];
var sortedWords_Array = words_Array;
sortedWords_Array.sort(function(a, b){
return b.length - a.length;
});
alert("sortedWords_Array is :"+sortedWords_Array);
alert("words_Array is :"+words_Array);
After I sort sortedWords_Array, they're both sorted for some reason. http://jsfiddle.net/zv39J/ Why is that?
Because in javascript, arrays are passed by reference which means that when you make one array equal to another it will change both whenever you make an edit to one. To fix this in your example change the following line:
var sortedWords_Array = words_Array;
to:
//make a copy of array by using slice
var sortedWords_Array = words_Array.slice(0);
See here for other uses and a more in-depth explanation:
slice does not alter the original array, but returns a new "one level
deep" copy that contains copies of the elements sliced from the
original array.
Array.prototype.slice()
Do a slice() on the array to duplicate, and then sort.
var sortedWords_Array = words_Array.slice();
Use the slice function to make a fast copy.
http://jsfiddle.net/zv39J/2/
On another note—alerts are terrible. No one wants to click a jsfiddle only to be greeted by a series of alert windows. I've updated the fiddle to include jQuery, which allows us to use a nice succinct syntax to make updates to the HTML.
This wont work because when you try to assign
var sortedWords_Array = words_Array;
The reference of words_array is passed to sortedWords_Array. So any change made to sortedWords_Array will be reflected on words_array.
Since Array.slice() does not do deep copying, it is not suitable for multidimensional arrays:
var a =[[1], [2], [3]];
var b = a.slice();
b.shift().shift();
// a is now [[], [2], [3]]
Note that although I've used shift().shift() above, the point is just that b[0][0] contains a pointer to a[0][0] rather than a value.
Likewise delete(b[0][0]) also causes a[0][0] to be deleted and b[0][0]=99 also changes the value of a[0][0] to 99.
jQuery's extend method does perform a deep copy when a true value is passed as the initial argument:
var a =[[1], [2], [3]];
var b = $.extend(true, [], a);
b.shift().shift();
// a is still [[1], [2], [3]]

Issue Reversing Array of Objects with JS

I'm parsing JSON and getting an array of objects with javascript. I've been doing this to then append an element for each object:
for(o in obj){ ... }
But I realized that for a certain situation I want to go backwards through the array. So I tried this before the for loop:
obj = obj.reverse();
However this isn't reversing the order of the objects in the array. I could simply put a count variable in the for loop to manually get the reverse, but I'm puzzled as to why reverse doesn't seem to work with object arrays.
There's no such thing as an "object array" in JavaScript. There are Objects, and there are Arrays (which, of course, are also Objects). Objects have properties and the properties are not ordered in any defined way.
In other words, if you've got:
var obj = { a: 1, b: 2, c: 3 };
there's no guarantee that a for ... in loop will visit the properties in the order "a", "b", "c".
Now, if you've got an array of objects like:
var arr = [ { a: 1 }, { b: 2 }, { c: 3 } ];
then that's an ordinary array, and you can reverse it. The .reverse() method mutates the array, so you don't re-assign it. If you do have an array of objects (or a real array of any sort of values), then you should not use for ... in to iterate through it. Use a numeric index.
edit — it's pointed out in a helpful comment that .reverse() does return a reference to the array, so reassigning won't hurt anything.
That's because the for (o in obj) doesn't iterate the array as an array, but as an object. It iterates the properties in the object, which also includes the members in the array, but they are iterated in order of name, not the order that you placed them in the array.
Besides, you are using the reverse method wrong. It reverses the array in place, so don't use the return value:
obj.reverse();

Categories