Referential equality: Where variables check-in and rarely check-out.
Tags: javascript, webdev, beginners, tutorial
Published: 2024-01-19 17:05:14

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
```
![Demonstration of the equalities of primitives with a funny meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ch6dd7n7lhmuvludssla.gif)
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
```

![Demonstration of the equalities of non-primitives with a funny meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gv222taf70atlnd4ywqi.gif)
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;
```

![Demonstration of the equalities of non-primitives with a funny meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hxugzw1ruzwqcgtiyhcb.gif)
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' ]
```
**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okep0vgoia23a3qu64mx.png)
Even though the `basket_1 `and `basket_2 `arrays 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lf40xhcr2eyqaict7eb9.png)
By assigning `let basket_3 = basket_1`, we forced the variable `basket_3 `to 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6s8zrfqzl7w2vozf3t1.png)
As `basket_3 `and `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']
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isewjagrlo26jg2qfqa9.png)
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 `inventory `object changes the `shop `object 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnx8ay8izar6pr2igl4f.png)
According to the above representation, changes made on `inventory `object have no impact on the `shop `object. 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
{% embed https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse %}
{% embed https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify  %}

---
Follow me on X, if you wish to see my progress in becoming a full-stack Java developer.
{% embed https://twitter.com/Subaashbala %}