Pass-By-Value or Reference in Javascript: A look into Primitives and Non-Primitives

pokumars

Oheneba Poku-Marboah

Posted on May 6, 2024

Pass-By-Value or Reference in Javascript: A look into Primitives and Non-Primitives

Spot the difference between these two samples of code and how things change.

// SAMPLE A
let a = 2;
let b = a;

console.log('a->', a, 'b->', b); // a-> 2 b-> 2

b= b*2;
console.log('a->', a, 'b->', b); // a-> 2 b-> 4
Enter fullscreen mode Exit fullscreen mode

and then take a look at this

// SAMPLE B
let a = {name: "Nelson M", age: 65 } ;
let b = a;
console.log('a->', a, 'b->', b);

// it affects the object that both a and b point to. This is because they both reference the same object in memory.
b.name = "Margaret Thatcher";
console.log('a->', a, 'b->', b); 
Enter fullscreen mode Exit fullscreen mode

They both end with changing variable b but look closely at how changing variable b affects a in both scenarios.
Do you see how variable a remains the same in Sample A but changes in Sample B even though we are seemingly doing the same thing in both scenarios? What is the reason for this discrepancy?

This is how data manipulation occurs in JavaScript. Changing a primitive and non-primitive data type in JavaScript happens differently.

What are primitives, you ask? According to MDN, "In JavaScript, a primitive (primitive value, primitive data type) is data that is not an object and has no methods or properties. There are seven primitive data types".
The primitive/non-primitive divide is the following in JavaScript

Type Examples
Primitive string, number, bigint, boolean, undefined, symbol, null
Non-primitive Array, Object

When you assign a primitive, it is assigned by value. Non-primitives are passed by reference when being assigned. By the way, "reference value" is the correct term, but you will often hear just reference, and I will use those two interchangeably. Passed by reference value means that the thing you want to assign has a memory address, and the variable points to that memory address. So, that means that if you have multiple variables pointing to the same memory location and you make a modification, it changes for all those variables that are "referring" to it - pun not intended. That is akin to having a shared collaborative Google doc - if I make a change, you see the change and vice versa. The doc I work on does not have a life of its own, independent from yours, unless you decide to copy/duplicate it. Copying or duplicating the Google doc into its own independent entity is what happens when you pass by value.

Let's revisit the examples we've discussed to reinforce our understanding of variable manipulation in JavaScript.

// SAMPLE A
let a = 2;
let b = a;

console.log('a->', a, 'b->', b); // a-> 2 b-> 2

b= b*2;
console.log('a->', a, 'b->', b); // a-> 2 b-> 4
Enter fullscreen mode Exit fullscreen mode

Sample A
The value 2 is assigned to the variable a. The same value that went to a is assigned to b, but it has its own memory address and is entirely independent from that of a.

// SAMPLE B
let a = {name: "Nelson M", age: 65 } ;
let b = a;
console.log('a->', a, 'b->', b);

// it affects the object that both a and b point to. This is because they both reference the same object in memory.
b.name = "Margaret Thatcher";
console.log('a->', a, 'b->', b); 
Enter fullscreen mode Exit fullscreen mode

Sample B
In non-primitives, a and b both point to the same thing. So when you change a property of b, it just changes the property of whatever in memory that b is pointing at. This happens to be the same thing that a is pointing at. Therefore, when you peek inside a, it will be identical to b because they really are the same thing.

So now let's make a little prediction -
So far, we know up to the part I wrote above. Let's see if we know enough to make a prediction based on what we have read. When you modify a primitive, e.g. number 2, it automatically is a reassignment to another thing. Because you are saying that the entirety of this 2 should change, unlike in the case of {name: "Nelson Mandela", age: 65 }, which has parts/properties that you can change.  The 2 is the one and only value in and of itself, while the non-primitive value has changeable parts. So, in essence, when you do b = 3, it is a reassignment of b to a whole new thing.

So, this then begs the question of how it works when I want to reassign non-primitive b to a whole new thing. The prediction is that the non-primitive would behave similarly to a primitive reassignment if you were to change the full value. As in, if you were to do b = {name: "Margaret Thatcher", age: 55 }, the a would remain the original and b would have its own new value.

Let's put that to the test

// non-primitive
let a = {name: "Nelson M", age: 65 } ;
let b = a;
console.log('a->', a, 'b->', b); 
// a-> { name: 'Nelson M', age: 65 } b-> { name: 'Nelson M', age: 65 }


// it affects the object that both a and b point to. This is because they both reference the same object in memory.
b.name = "Margaret Thatcher";
console.log('a->', a, 'b->', b); 
// a-> { name: 'Margaret Thatcher', age: 65 } b-> { name: 'Margaret Thatcher', age: 65 }


// you are reassigning b to a completely new object. Doesn't affect a because a is still pointing to the first object
b= {name: "Margaret T", age: 55 };
console.log('a->', a, 'b->', b); 
// a-> { name: 'Margaret Thatcher', age: 65 } b-> { name: 'Margaret T', age: 55 }
Enter fullscreen mode Exit fullscreen mode

The prediction checks out. So, in the case of reassignment, things work the same for both primitives and non-primitives; a new value is given. The variable is assigned a new value - unless you are assigning another variable e.g. c to b, in which case the same passing by reference value principle as mentioned above applies.

Let's go one step further. What happens when b = a but then later, b = c? Does changing c or b in this case affect a?

// non-primitive
let a = {name: "Nelson M", age: 65 } ;
let b = a;
console.log('a->', a, 'b->', b);
// a-> { name: 'Nelson M', age: 65 } b-> { name: 'Nelson M', age: 65 }

// it affects the object that both a and b point to. This is because they both reference the same object in memory.
b.name = "Margaret Thatcher";
console.log('a->', a, 'b->', b);
// a-> { name: 'Margaret Thatcher', age: 65 } b-> { name: 'Margaret Thatcher', age: 65 }

let c = {name: "Donald Duck", age: 31 };

b=c;

// c is passed by reference value to become what b is pointing to. a remains intact
console.log('a->', a, 'b->', b,'c->', c); 
// a-> { name: 'Margaret Thatcher', age: 65 } b-> { name: 'Donald Duck', age: 31 } c-> { name: 'Donald Duck', age: 31 }


//modify both b and c which should both be the same thing and thus should contain both changes
b.name= b.name+'1'
c.name= c.name+'1'
console.log('a->', a, 'b->', b,'c->', c);
// a-> { name: 'Margaret Thatcher', age: 65 } b-> { name: 'Donald Duck11', age: 31 } c-> { name: 'Donald Duck11', age: 31 }
Enter fullscreen mode Exit fullscreen mode

As you can observe in the code snippet above, the answer is no. This is because the memory address that b is pointing to has been updated to be the address of what c is pointing to. This means that when b or c are modified, it modifies the same thing but a remains intact. Understanding this behaviour is crucial in JavaScript programming as it directly affects how your code operates.

We have examined how variable assignment is not uniform for all data types in JavaScript. We have also introduced the concepts of primitive and non-primitive data types and passing by reference or by value.

Check out this code snippet below and try to figure out what happens to the variables. Before reading the answer and explanation, think about it and take a guess. In the comments, let me know what you think would happen to a, b and c outside the function. Here is the Stack Overflow link where you can find out more.

 

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);
Enter fullscreen mode Exit fullscreen mode

Feel free to reach out and talk moreLinkedIn Github

PS: Always show up to your flight's gate at least 40 minutes before the departure time. I just missed one. I am writing this with a lighter wallet while stranded in the airport for 24 hours waiting for the next available flight.

javascript

programming

coding

datastructures

💖 💪 🙅 🚩
pokumars
Oheneba Poku-Marboah

Posted on May 6, 2024

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

Sign up to receive the latest update from our blog.

Related