JavaScript object property gets modified in a very interesting way - javascript

I have put together a small test, can someone explain why does JavaScript do the following?
Why will testArr inherit the modifications of newArray?
var TestClass = function() {
this.testArr = [10,11];
this.addToArray = function() {
var newArray = this.testArr;
newArray.push(12);
}
}
var testClass = new TestClass();
testClass.addToArray();
console.log(testClass.testArr); // will be [10, 11, 12]

Because they're the same array. Variables contain references to arrays. You're assigning that reference to a new variable, and now both variables point to the same array.
Here's a much simpler test which reproduces the behavior:
x = [1, 2] // the value of x is a reference to an array
y = x // the value of y is a reference to the same array
y.push(3) // modify the array pointed to by both variables
console.log(x) // [1, 2, 3]
If you want to create a new array, you need to clone the array.

With following statement
var newArray = this.testArr;
You will not copy the values from inside testArr. You will create a reference to this array. When you make an adjustment to a reference you will automatically change the referenced source too.
When you want to copy the values from testArr to another variable you can do this instead.
var newArray = this.testArr.slice();
This will copy the values from testArr inside newArray.

Related

Manipulating a const array

Why JS allows mutating const arrays?
e.g.
const a = 5
a = 6 //throws error
const arr = [1,2,3]
arr[2] = 4 // no error
Why is it allowed to mutate a const array when it should throw an error as in the first case?
Also how do I ensure that my array remains completely immutable?
It is allowed to add, delete or mutate const array in JS because arr variable stores the memory reference and not the values.
So even if you manipulate the values at that memory location you are not really changing the reference to the memory and hence the variable remains const.
The only thing that is not allowed is re-assigning the array arr = []
which essentially means changing the memory reference the arr variable stores.
As #Nina stated any kind of assignment to const variables is not allowed.
const arr = [1, 2, 3]
arr[2] = 4 //this is fine as memory reference is still the same
arr[3] = 3
console.log(arr)
arr = [] //throws error
console.log(arr)
A scalar value in Javascript is stored by value. For example:
let a = "foo" <--- a is linked to the value "foo"
let b = "foo" <--- b is also linked to the value "foo"
Objects and arrays are stored by reference. For example:
const arr = [1,2,3] <--- arr is linked to a unique address in memory that references 1, 2, and 3
When an array or object is mutated, the address in memory doesn't need to change:
arr.push(4) <-- arr is now [1,2,3,4], but its address in memory doesn't need to change, it's still just an array that references 1, 2, 3, and now 4

How do I load the values from one array, into another array, without changing the memory address of the array receiving the new values?

If I do this:
a = []
b = [1, 2, 3]
a = b
a will then refer to b
I want to copy all of the values in b into a without changing any memory address/references.
You could push the values without changing the reference from a or b.
var a = [],
b = [1, 2, 3];
a.push(...b);
If you want to populate a in-place, without ever discarding the initial array, then you can simply loop over b and add all the items in a:
var a = []
var b = [1, 2, 3]
var firstA = a; //get a initial element
b.forEach(function(item) {
this.push(item)
}, a);// <-- pass `a` as the `this` context
console.log("`a` is unchanged", a === firstA);
console.log("`a` is not `b`", a !== b);
console.log(a);
let a = b.map(x => x);
The .map() function will create a new array with values generated from the source elements via the passed-in callback function.
As noted in a comment
let a = b.slice();
is pretty much exactly the same.
Basically this saves you the step of
let a = [];
since one brand-new empty array is as good as another. However, if you really have your heart set on using the empty array explicitly initialized, you could use
b.forEach(x, i) { a[i] = x; });
A= b.slice(). It will create a shadow copy of element without changing original one. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

Array as a Variable Assignment in Javascript

If:
var x = [1, 2, 3];
var y = [4, 5, 6];
var z = x;
and then if z[2] = y[0];
Why is it that console.log(x); is [1, 2, 4] and not [1, 2, 3]?
When you do var z = x; you are no creating a new array, entirely separate from x, you are simply creating a reference to the original array. Hence, the change happens in both.
If you want to create a new object, you can use the new ES6 spread operator
var z = {...x};
Have a look at this answer for a more in-depth explanation of passing by reference and by value.
Cuz the 'z' variable is a pointer to the same array that 'x' point to.
An array in JavaScript is also an object and variables only hold a reference to an object, not the object itself. Thus both variables have a reference to the same object. So changing made through one variable reflected in other as well.
var x = [1, 2, 3];
var y = [4, 5, 6];
var z = x;
z[2]=y[0];
console.log(x);
var w=Object.assign([],x);
w[0]=y[1];
console.log(x);
console.log(w);
Look at the example. If you want to change in a new variable and don't want reflected that change in original one than use Object.assign.

array.concat vs angular.copy

I've noticed that [].concat() performs similarly to angular.copy() with arrays. For instance,
var x = [5, 2];
var y = [].concat(x);
// y = [5, 2]
x = [2, 3];
// y = [5, 2]
var z = angular.copy(x);
// z = [2, 3];
x = [6, 9];
// z = [2, 3];
Is there a key difference between [].concat() and angular.copy(src,[dest])?
angular.copy performs a deep copy of the source and places it on the destination (Both the arrays of source and dest and its contents, even reference types, points to different reference location). But when you do [].concat (Both the arrays of source and dest points to different reference and its reference type contents points to the same reference), it just returns a new array, so only think that is similar in using both angular.copy and [].concact in your example is that it assigns a new reference of the array object to the lhs variable.
But consider the situation where you have array of objects.
$scope.arr = [{name:'name1'}, {name:'name2'}, {name:'name3'}, {name:'name4'}];
$scope.arrConcat = [].concat($scope.arr); //Get a new array
$scope.arrCopy = angular.copy($scope.arr); //Get a new copy
//Now change the name property of one of the item
$scope.arr[3].name="Test";
//Now see who all have been changed
console.log($scope.arr[3].name); //Of course will output "Test"
console.log($scope.arrConcat[3].name); //Will also output "Test" because the new array items still holds the same reference of the objects.
console.log($scope.arrCopy[3].name); //Will output name4 because this contains another reference which holds the copy of the value of the object at index 3 from source array
//Push something on to the original array
$scope.arr.push({name:'name5'});
//You will see that new item is not available here, which is exactly the behaviour that you are seeing in your case because both the arrConcat and arrCopy holds its own version of array through the items in arrConcat and arr are from the same reference.
console.log($scope.arrConcat);
console.log($scope.arrCopy);
So only thing is that in your case [].concat is kind of a convenient method to get a copy of the source array since your array just has primitives, so no issues.
Example - Demo
http://plnkr.co/edit/06zLM8g34IDBLUmPtwV2?p=preview
var x = [5, 2];
var y = [].concat(x);
// y = [5, 2]
var x = [5, [5, 2]];
var y = [].concat(x);
// y = [5, 2]
Check this out, [].copy() never does a deep copy unlike angular.copy()

Reference in slice in javascript does not exit.

I am getting back to JS after a very long break and as I was going over "Jump Start JavaScript" book. In part of it says:
if your array contains an array as one of its items, it will be
copied by reference. In other words, if the original array changes, so
will the copy.
So I tried to do in code but JS doesn't work! (correct me if I'm wrong)
Then in SOF I saw this thread:
Reference to slice of an array
Should I consider this as mistake/error in the book?!
Thanks,
AK
It's unfortunate that the phrase by reference gets thrown around like it does, because it has a perfectly clear meaning that is completely different from one the being (mis-) used here. All values in JavaScript are copied/passed by value. When dealing with objects, that value is a reference to the object. But it's still a value that is copied.
When you make a shallow copy of an array using slice, the elements that are references to objects have those same references copied to the new array. That is, the same position in both arrays will refer to the same object. So, yes, changing one changes the other because there is no other; they are the same object.
Proof:
var arr = [[7, 6, 5], 4, 5];
var copy = arr.slice();
arr[0][0] = 88;
console.log(copy[0][0]); // 88
Tangent on pass-by-reference
If you can't write a function like this:
function swap(arg1, arg2) {
var temp = arg1;
arg1 = arg2;
arg2 = temp;
}
...that actually swaps the values of two variables:
var one = [1, 2, 3];
var two = ["a", "b", "c"];
swap(one, two);
console.log(one) // same as before
...then your language does not support pass by reference.
More pass-by-value
Wait, doesn't the following demonstrate that JavaScript must be pass-by-reference:
function Test(arg1) {
this.arg = arg1;
}
var one = [1, 2, 3];
var x = new Test(one);
x.arg[0] = 5;
console.log(one); // it's been updated!
No! If you think this then you still haven't understood what pass by reference actually is. Yes, a reference to the original array was passed to Test, but it was passed by value. In other words, x.arg refers to the same object in memory as one but it is not an alias for one. To prove this, you simply need to assign something new to x.arg:
x.arg = ["new array"];
This does not affect one. These two variables are independent. JavaScript is strictly pass-by-value. There are no exceptions.
In JavaScript it´s also correct to initialize an Array with the "new" keyword:
var arr = new Array();
arr.push("0. Index");
arr.push("1. Index");
document.body.appendChild(document.createTextNode(JSON.stringify(arr)));
In every programming language I know, the new keyword stands for creating an object. And also in every programming language I know, an object is Call-By-Reference and not Call-By-Value. So the book tells you the truth. That´s a JavaScript special: Because PHP arrays are always Copy-By-Value, if you do not use explicit the & for making it to a reference.
To clear this this disussion finally, execute this code below in you browser and compare the used memory size. A 2nd reference to an already existing object should cause lower memory usage than 2 seperated arrays:
First this one:
var arr1 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
}
var arr2 = arr2;
Second this one:
var arr1 = new Array();
var arr2 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
arr2.push(i);
}
Or simply try this one and see the results:
var arr1 = new Array();
for (var i = 0; i < 11; i++)
{
arr1.push(i);
}
var arr2 = arr1;
arr2[5] = "test";
document.body.appendChild(document.createTextNode("arr2 => " + JSON.stringify(arr2)));
document.body.appendChild(document.createTextNode("arr1 => " + JSON.stringify(arr1)));
If the output prints on the 5th index for both arrays "test" instead of 5, it´s for sure Call-By-Reference, as your book said. If not, it´s Copy-By-Value.
var subarray = [1, 2, 3];
var main_array = ['a', subarray, 'b'];
var slice = main_array.slice(0, 2);
console.log(slice); => ['a', [1, 2, 3]]
subarray[0] = 10;
console.log(slice); => ['a', [10, 2, 3]]

Categories