Pass by Value, Pass by Reference & How to clone JavaScript Objects
--
First, let's look at how values are passed, copied, and stored in memory by JavaScript.
In JavaScript, there are two types of Values.
- Primitive Values
This is the data that has a single value and no additional methods or properties. E.g. String, Number, Boolean, Undefined, and Null. These data types are passed by value.
2. Reference Values
All Objects in JavaScript are reference types. E.g. Objects, Arrays, Classes, etc. These data types are passed by reference.
When we assign a primitive value to a variable, the actual data is stored in a memory called the “STACK” and the variable holds the address or the position of the data in the STACK.
When we assign a reference value to a variable, the actual data is stored in a memory called the “HEAP”. The memory address of this data is then copied on the STACK and the variable holds the position of this address in the STACK.
// Primitive Typeslet name = 'John';
let copyOfName = name;name = 'Adam';console.log(name); // => 'Adam'
console.log(copyOfName) // => 'John'// Reference Typeslet apple = {name: 'Apple', price: 120}
let copyOfApple = apple;apple.price = 130;console.log(apple) // => {name: 'Apple', price: 130}
console.log(copyOfApple) // => {name: 'Apple', price: 130}
Notice how copyOfName
doesn’t change after changing the original name
variable but in the case of apple
& copyOfApple
, both the objects are changed. This happens because Primitive Values are passed by value and objects are passed by reference.
When we copy a variable into another, a copy of the data on the stack is created and assigned to the new variable.
So if we create a copy of a primitive type, a copy of the data which is on the STACK, in this case, the actual data is copied
But if we copy a reference type, a copy of the data which is on the stack is created, in this case, the address of the data is copied and assigned to the new variable. This explains why modifying a copied object also modifies the original object.
The same is true when passing values to a function.
function square(num) {
num = num*num;
return num;
}function increasePriceByTen(fruit) {
fruit.price += 10;
}let five = 5;
let squareOfFive= square(n1)
console.log(n1); // => 5
console.log(n2); // => 25let apple = {name: 'Apple', price: 120}
let copyOfApple = appleincreasePriceByTen(apple);console.log(apple.price); => // 130
console.log(copyOfApple.price) // => 130
Passing a value to the function square(five)
copies data in variable five
and stores it in the variable num
. Again a copy of the return data is created and store in the variable squareOfFive
. Hence, primitive types are passed by value.
Passing an object to the function increasePriceByTen(apple)
copies the address of the data in variable fruit
. At this point, all three variables apple
, copyOfApple
, fruit
are pointing at the same object which is stored in the HEAP. Therefore, changing one object also changes all the other objects because they are pointing at the same object.
Now that we know what primitive values and reference values are, let’s look at how to create true copies of JavaScript Objects.
Several techniques can be employed to correctly clone an object, for starters we can use our trusty fo...in
loop to iterate through all the properties of an object and copy them into another.
let fruits = ['apple', 'banana', 'orange'];
let copyOfFruits = [];for (index in fruits) {
copyOfFruits[index] = fruits[index];
}// for arrays we can also use the `splice` method
let anotherCopyOfFruits = fruits.splice(0);let basket = {
capacity: 200,
contents: ['banana', 'cherry']
}
let copyOfBasket = {};for (key in basket) {
copyOfBasket[key] = basket[key];
}
Other ways include using the Object.assign()
method or use the spread operator ...
.
let copyOfFruits = Object.assign([], fruits);
let copyOfBasket = Object.assign({}, basket);let thirdCopyOfFruits = [...fruits];
let thirdCopyOfBasket = {...basket};
But all the above techniques are creating what is called Shallow Copy of the objects. What is a Shallow Copy? Let’s continue with the example code above
basket.contents.push('mango');console.log(basket.contents);
// => ['banana', 'cherry', 'mango']console.log(copyOfBasket.contents);
// => ['banana', 'cherry', 'mango']
See the problem here? Thebasket
object contains another object of type Array
. The above methods will create a copy of the objects nested inside the original object. The same is true for Arrays.
let nestedArray = [ [ 1, 2, 3 ] ];
let copiedArray = Object.assign([], nestedArray);nestedArray[0].push(4);console.log(nestedArray);
// => [ [ 1, 2, 3, 4 ] ]console.log(copiedArray);
// => [ [ 1, 2, 3, 4 ] ]
To create Deep Copy we can use the following technique
let basket = {
capacity: 200,
contents: ['banana', 'cherry']
}let deepCopyOfBasket = JSON.parse(JSON.stringify(basket));
Using the JSON.parse
and JSON.stringify
methods we can easily create Deep Copies of Objects.
We can also use the methods exposed by the Lodash library like the _.clone()
&_.deepClone()
to create Deep Clones of JavaScript Objects.