In this post, we will try to understand how JavaScript works a bit different than other programming languages when it comes to data manipulation. We will primarily look at two fundamental ways of manipulating data i.e. Call By Value
and Call By Reference
.
Data Types
To start with, we need to know different data types in JavaScript. It can be divided into two groups, Primitive
and Non-Primitive
.
Primitive
Number
String
Boolean
Undefined
Null
Non-Primitive
Object
Array
Knowing these data types will help us understand better how they are processed by the JavaScript interpreter.
By Value
When a variable is passed by value to a function then a copy of the variable is passed. The original and the copy are two independent values that are stored separately. Any changes made to the copy inside the function does not change to the original value. It holds for all primitive data types. However in JavaScript, everything is passed by value (More on this later).
var original = 10;
function modify(copy) {
copy += 20;
return copy;
}
var modified = modify(original);
console.log(original, modified); // 10 30
Here, we did pass the variable to the function and made some changes to it. However, the original value remained the same due to the fact function scope maintained a local copy of the passed argument and all changes were made to that copy which does not change the original value.
By Reference
When a variable is passed by reference then there is only one actual copy of the value. There can be multiple references to that value but never duplicate(s) of that value.
var first = { name: "Yomesh" };
var second = first;
console.log(first, second); // { name: 'Yomesh' } { name: 'Yomesh' }
second.name = "Ajay";
console.log(first, second); // { name: 'Ajay' } { name: 'Ajay' }
As you can see in the above code snippet the second
variable holds a reference to the value (assignment made by reference). Due to this fact, changes made to the value by one reference are reflected across all the references. A similar situation happens when a variable is passed by reference to a function.
var person = { name: "Yomesh" };
function modify(person) {
person.name = "Ajay";
return person;
}
var newPerson = modify(person);
console.log(person, newPerson); // { name: 'Ajay' } { name: 'Ajay' }
The variable person was passed by reference to the function and changes were made to the value using the passed reference. Hence, it is visible outside the function also i.e. original value is modified. It holds for all non-primitive values (Objects and Arrays).
Note: The following is very important. So, read it carefully and understand it.
The basic rule would be that the primitive types are passed by value and the non-primitive types are passed by reference. However, earlier, it was mentioned that in JavaScript everything is passed by value but our current findings contradict that! Well, not exactly, in terms of non-primitive types, variables are also passed by value. However, the passed value is a reference to the variable rather than the variable itself.
var person = { name: "Yomesh" };
// passed by value where value is a reference (Step 1)
function modify(newPerson) {
newPerson.name = "Ajay"; // original value is modified via reference (Step 2)
}
modify(person);
/** ====== Illustrations ======
----------------------
| { name: 'Yomesh' } | // Step 1
----------------------
| |
person ---------| |
|
newPerson ----------|
----------------------
| { name: 'Ajay' } | // Step 2
----------------------
| |
person ---------| |
|
newPerson ----------|
**/
Argument newPerson
maintains a reference to the original value due to which it can change the original value and it is reflected everywhere. However, if the reference is altered in some way then it is not visible outside.
var person = { name: "Yomesh" };
// passed by value where value is a reference (Step 1)
function modify(newPerson) {
newPerson = { name: "Ajay" }; // reference is overwritten with a reference to a new value (Step 2)
}
modify(person);
/** ====== Illustrations ======
----------------------
| { name: 'Yomesh' } | // Step 1
----------------------
| |
person ---------| |
|
newPerson ----------|
---------------------- ----------------------
| { name: 'Yomesh' } | | { name: 'Ajay' } | // Step 2
---------------------- ----------------------
| X |
person ---------| X |
X |
newPerson ----------|-----------------------------|
**/
Use-cases
As we have seen modifying via reference leads to changes in the original value. We can rectify this by creating a copy.
...
function processing(input, value) {
var copy = [...input];
copy.push(value);
return copy;
}
...
var newPeople = processing(people, { name: 'prithvi' });
console.log(people, newPeople);
// [{ name: 'yomesh' }, { name: 'ajay' }]
// [{ name: 'yomesh' }, { name: 'ajay' }, { name: 'prithvi' }]
However, copying via spread operator
or Object.assign
creates a shallow copy which can lead to further complications. Hence, it is recommended to always deep clone when you have nested values and want to modify data.
var data = { name: "prithvi" };
var people = [{ name: "yomesh" }, { name: "ajay" }];
function processing(input, value) {
var copy = [...input];
copy.push(value);
copy[copy.length - 1].name = "joker";
return copy;
}
var newPeople = processing(people, data);
console.log(people, newPeople, data);
// [{ name: 'yomesh' }, { name: 'ajay' }]
// [{ name: 'yomesh' }, { name: 'ajay' }, { name: 'joker' }]
// { name: 'joker' }
Here, in the above code snippet, even after making a copy the changes are visible outside. To fix this, we can deep clone using any suitable method.