Referential equality: Where variables check-in and rarely check-out.

subaash_b

Subaash

Posted on January 19, 2024

Referential equality: Where variables check-in and rarely check-out.

Hi fellow devs, we are here to dive deep into the heart of JavaScript's strict equality operator ('===') and figure out why some variables behave like identical twins while others behave like awkward strangers in an alumni reunion.
Before knowing about the equality operator, we should understand what are primitive values and non-primitive values in JavaScript.

Primitive values

Primitives are basic, fundamental data types that serve as the building blocks to build a complex data type. The following are some primitives in JavaScript.

  • String

  • Number

  • Boolean

  • Undefined

  • Null

Non-Primitives

Any data type other than the primitives are Non-Primitives. Some of them are,

  • Array

  • Object

  • Function

  • RegExp

  • Set

  • Map

  • Promise

Examples

1) Primitives and the effect of Equality
Whenever we strictly equate('===') two primitives of a similar data type, they always yield true.

let fruit_1 = 'apple'
let fruit_2 = 'apple'
console.log(fruit_1 === fruit_2) //Output: true
Enter fullscreen mode Exit fullscreen mode

Demonstration of the equalities of primitives with a funny meme
2) Non-Primitives and the effects of Referential Equality
When two non-primitives of the same data type are strict-equated, the results will be surprising. Look at the following snippet where two arrays with similar data types and similar items are equated.

let basket_1 = [ 'apple', 'banana' ]
let basket_2 = [ 'apple', 'banana' ]
console.log(basket_1 === basket_2) //Output: false
Enter fullscreen mode Exit fullscreen mode

Demonstration of the equalities of non-primitives with a funny meme
But, when we check the equality criteria of basket_3 and basket_1, it returns true

let basket_1 = [ 'apple', 'banana' ]
let basket_2 = [ 'apple', 'banana' ]
let basket_3 = basket_1
console.log(basket_3 === basket_1) //Output: true;
Enter fullscreen mode Exit fullscreen mode

Demonstration of the equalities of non-primitives with a funny meme
Let's try one more thing.
When we push another item into basket_3, it should not affect basket_1. But it does.

let basket_1 = [ 'apple', 'banana' ]
let basket_2 = [ 'apple', 'banana' ]
let basket_3 = basket_1
basket_3.push('orange')
console.log(basket_1) //Output: [ 'apple', 'banana', 'orange' ]
console.log(basket_3) //Output: [ 'apple', 'banana', 'orange' ]
Enter fullscreen mode Exit fullscreen mode

But why is this happening?
This is where we should know about Referential equality.

Referential Equality

This seems to be a Jargon, doesn't it? Actually No.
Referential Equality is like asking, "Are these two non-primitive things pointing to the same spot in the computer's memory ?" If yes, they are referentially equal(i.e., true) otherwise, they aren't(i.e., false). As simple as that.
Take a look at the following representation to get it better.
Pictorial representation of arrays occupying various memory locations though they contain the same item in them
Even though the basket_1and basket_2arrays have similar items, they occupy different locations in memory. When we use strict equality (===) to compare them, JavaScript checks whether they refer to the same location in memory, and since they are on distinct memory locations, the result is false.

But, things changed when we declared basket_3 and assigned it to basket_1

Pictorial representation of arrays sharing the same memory location
By assigning let basket_3 = basket_1, we forced the variable basket_3to point to the exact memory address as basket_1. So the result was true when we did console.log(basket_3===basket_1).
Pictorial representation of arrays sharing the same memory location
As basket_3and basket_1 point the same memory location, changes made through one of the variables will reflect in the other. Therefore, when we push the item 'orange' only into basket_3, we observe that the item 'orange' is now present in both arrays

However, for primitive types like strings, numbers etc., we are more interested in comparing their values rather than their references. JavaScript decides whether to compare a variable by the value or by its reference based on its primitive and non-primitive nature.

How to work with Non-primitives?

How do we work with them if they keep sharing their addresses? JavaScript has nothing to do with this. Even some of the libraries like React, utilize this uniqueness of the non-primitives to control the component renders.
But, we can prevent them from sharing memory locations or memory addresses.
If the array is on the top level (i.e., no nesting within arrays) we can shallow copy the existing array into the other.
Create a shallow copy using the spread operator (...)

let fruit_1 = ['apple'];
let fruit_2 = ['apple'];
let fruit_3 = [...fruit_1]
fruit_3.push('orange')
console.log(fruit_3) //Output: ['apple', 'orange']
console.log(fruit_1) //Output: ['apple']
Enter fullscreen mode Exit fullscreen mode

Now you may get a question. What about the other non-primitives? Will this even work with JavaScript objects? The answer is, YES.
Let's take a complex example of an object whose values are nested deeper than the top level.
Demonstration of failing copy fail due to the deeply nested object
Wait, What? The shallow copy is supposed to copy the contents into a new memory address. But in the above case, making changes in the inventoryobject changes the shopobject as well. So, this is the catch with the shallow copy. You cannot use shallow copy with deeply nested objects and arrays.
We can overcome this by using JSON.stringify() and JSON.parse()
Picture representing the deep copy of objects
According to the above representation, changes made on inventoryobject have no impact on the shopobject. And this is how we perform deep copy of nested objects.
If you are confused with JSON.parse() and JSON.stringify(), please refer to the official documentation available at the end of this article.
Using JSON methods has its drawbacks. They can be rectified by using appropriate methods and techniques. Given the article's length, let's wrap up our discussion here and I look forward on to delving another article, discussing the alternatives to the JSON methods. Thank you for reading and have a nice day.


Here are the official MDN docs on JSON.parse() and JSON.strigify() methods

JSON.parse() - JavaScript | MDN

The JSON.parse() static method parses a JSON string, constructing the JavaScript value or object described by the string. An optional reviver function can be provided to perform a transformation on the resulting object before it is returned.

favicon developer.mozilla.org

JSON.stringify() - JavaScript | MDN

The JSON.stringify() static method converts a JavaScript value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.

favicon developer.mozilla.org

Follow me on X, if you wish to see my progress in becoming a full-stack Java developer.

💖 💪 🙅 🚩
subaash_b
Subaash

Posted on January 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related