Comparison by value vs reference
When writing conditionals in our daily development work, we often need to check if two values are equal. If they are, we’ll do something; otherwise something else will be executed. For example:
const id = 256;
if (id === 256) {
console.log('This item has an id of 256.');
} else {
console.log('Unknown id.')
}
We will see the “This item has an id of 256.” message in the console when running the code, so really no surprise here.
But the case is not that simple when we need to compare more complex instruments like objects or arrays. Let’s discuss then the difference between comparing by value vs reference.
Comparing by value
Number and string types are compared by value.
Numbers
When a variable is declared and it’s assigned a number, it will “book” its dedicated spot in the memory. Consider the following assignment:
const firstNumber = 5;
Imagine that all numbers JavaScript can handle are wired to an imaginary lightbulb on a long shelf. When a variable is assigned a number, the corresponding bulb will be turned on. One lightbulb always belongs to one and only one number. As a consequence number 6 is wired to a different lightbulb from number 5, number 128.62 is connected to another bulb again and so on.
Now let’s create a second variable and assign it the same number:
const secondNumber = 5;
console.log(firstNumber === secondNumber); // true
The equality will be true, because both firstNumber
and secondNumber
variables is connected to the same lightbulb, i.e. the same spot in memory, which belongs to 5
. This is why the following statements will always return true
:
const thirdNumber = firstNumber;
console.log(thirdNumber === firstNumber); // true
console.log(thirdNumber === secondNumber); // true
We make thirdNumber
equal to firstNumber
and don’t explicitly assign 5
to it. But the type of firstNumber
is number, and numbers are compared by value.
As a result firstNumber
, secondNumber
and thirdNumber
are all wired to the very same imaginary lightbulb, which belongs to 5
, so they will prove to be equal.
This is called comparison by value.
Strings
Strings behave similarly to numbers:
const firstTree = 'tree';
const secondTree = 'tree';
console.log(firstTree === secondTree);
const thirdTree = firstTree;
console.log(thirdTree === firstTree);
console.log(thirdTree === secondTree);
The string tree
receives a dedicated place in the memory and however a variable is referred to the word tree
(either by direct assignment or referencing to a variable), as long as the strings are the exact match, the equalities will always return true
.
Quite a few lightbulbs are needed to cover all numbers and strings!
Comparing by reference
Objects and arrays behave differently as they are compared by reference.
Objects
This means that each object will create their own space in memory when they get declared, regardless of their keys and values.
Consider the following object:
const obj1 = { a: 1, b: 2 };
{ a: 1, b: 2 }
will turn on its own bulb and the obj1
variable will be wired to it. If we create an obj2
variable and make it equal to obj1
, the equality will be true
:
const obj2 = obj1;
console.log(obj1 === obj2); // true
Why?
Because obj2
refers to obj1
and obj1
refers to the spot dedicated to the { a: 1, b: 2 }
object in memory. In terms of bulbs, obj2
is connected to obj1
and obj1
is wired to the lightbulb of { a: 1, b: 2 }
. So far it seems to be the same scenario as above.
Now what happens when we create an obj3
variable and assign { a: 1, b: 2 }
to it?
const obj3 = { a: 1, b: 2 };
console.log(obj1 === obj3); // false
The equality check returns false
, which means they are not equal!
The reason is that obj3
is assigned to another object, { a: 1, b: 2 }
. Even if both obj1
and obj3
have the same keys and values, each object literal will create it’s own space in memory when assigned to a variable. This means that we now have two bulbs, one for each { a: 1, b: 2 }
.
When an equality check occurs with ===
(or even with ==
), it’s like checking if the variables are wired to the same lightbulb.
In the case of obj1
and obj2
, they are. obj2
is made equal to obj1
and therefore both refer to the same bulb. (Yeah, I’ll keep using the bulb theme here. Feel free to change it to something else.)
However, we assigned a new object to obj3
, even if that object contains the same properties and values as obj1
’s. New object literals create their own spot in memory (i.e. they will be wired to a different lightbulb), hence obj3
will not be equal to either obj1
or obj2
.
It’s no surprise now that any reference to obj3
will make the equality statement return true
:
const obj4 = obj3;
console.log(obj4 === obj3); // true
console.log(obj4 === obj2); // false
On the contrary obj2
has a reference to the { a: 1, b: 2}
of obj1
, so the last statement will return false
.
Or even easier:
console.log({ a: 1, b: 2 } === { a: 1, b: 2 }); // false
console.log({ a: 1, b: 2 } == { a: 1, b: 2 }); // false
It’s definitely not about using ===
over ==
!
Arrays
Arrays behave the same way as objects:
const arr1 = [2, 'tree'];
const arr2 = [2, 'tree'];
const arr3 = arr2;
console.log(arr2 === arr3); // true
console.log(arr1 === arr2); // false
New arrays also demand their own spot it memory, and they are also compared by reference.
Conclusion
To sum up, strings and numbers are compared by value, but objects and arrays are compared by reference.
New object literals and arrays create their own place in memory, hence they won’t be equal even if they seem to be the exact copy of each other.
On the other hand, strings and numbers are compared by value and they refer to the same place in memory.
Thanks for reading and see you next time.