Skip ahead!
One of the most pivotal moments in my career as a software developer was when it finally clicked for me what the difference was between value types and reference types. It is definitely a huge hurdle to get over, but once you feel comfortable with it, youāll find the confidence you have in your code skyrocket, and youāll be able to use the different types to your advantage.
So, what do we mean by all this? Letās first discuss value types.
Value types
In JavaScript, there are 6 primitive types:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol
When a variable is declared and initialised with one of these types, it holds the absolute value. This means that if we do something like this:
let a = 'hello!';
let b = 'hello!';
console.log(a === b);
We would get true
logged in the console, because the two strings are identical.
If we introduced a new variable, c
, assigned the value of a
to c
, and then we modified a
, c
would maintain the value that it was given when it was initialised.
const c = a;
a = 'goodbye!';
console.log(c); // 'hello!'
Reference types
For other types, most commonly arrays and objects, they do not hold an absolute value. Instead, they hold an address, or a pointer.
Comparing two different arrays
Letās create two arrays that hold the same sequence of numbers.
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false
In our console we will see false
. But they both have the same values, so why is that?
Well, when we initialise our arrays, we actually allocate a slot in the memory for them, and our variable holds the information to be able to access that address. Even though they both hold the same data, they are stored in different locations.
A real world example
Imagine that there are two people that want to use lockers at an airport. Letās give them names - how about John and Jade?
John goes to the locker attendant, and asks for a key. The locker attendant gives him a key to locker 50
. He puts a camera, phone, and watch in the locker.
const locker50 = [];
const johnsLocker = locker50;
johnsLocker.push('camera', 'phone', 'watch');
Jade also goes to the locker attendant, and the locker attendant gives her a key to locker 60
. She also puts a camera, phone and watch in the locker.
const locker60 = [];
const jadesLocker = locker60;
jadesLocker.push('camera', 'phone', 'watch');
It is not the same locker, and the only way to compare the two lockers is to open them up and compare each item one by one.
When comparing arrays in javascript, the array is the locker. Our array variables are storing the locker number
, a.k.a the address in memory that the contents of the locker are held.
Duplicating an array
On the flipside, what if we want to duplicate an array?
const arr1 = [1, 2, 3];
const arr2 = arr1;
// arr2 now points to the same memory location as arr1
arr2[0] = 1000;
console.log(arr1);
console.log(arr2);
What do you think will be the output when we log both of these arrays?
[1000, 2, 3];
[1000, 2, 3];
They will both log the same. When we changed the value of the first element in arr2
, we didnāt change anything about the address of the variables - the two variables are still pointing to the exact same position in memory. However, the data inside that memory location has changed.
Similarly, if we compare the two arrays:
console.log(arr1 === arr2);
The output will be true
, because they are still both pointing to the same location in memory.
A real world example (again)
Letās revisit our airport example. This time, our two people requiring lockers are from the same family.
John asks for a locker, and the locker attendant gives him a key to locker 10
.
const locker10 = [];
const johnsLocker = locker10;
He puts his camera, phone and watch in there.
johnsLocker.push('camera', 'phone', 'watch');
His sister, Jade, sees him doing this and asks if they can share. The generous locker attendant fetches another key from the back that is also for locker 10
.
const jadesLocker = locker10; // jadesLocker & johnsLocker now point to the same address in memory
Jade then puts her headphones in the locker and closes it.
jadesLocker.push('headphones');
This time, it is the same locker. The locker number is the same, and when either of them adds something or takes it out, it changes what is in the locker for the other person.
console.log(locker10); // ['camera', 'phone', 'watch', 'headphones'];
In our code, when we assign a variable holding an array to another variable, we are just making a copy of the address, which allows both variables to access the same array.
If Jade decided that she needed her own locker, we could simply do:
jadesLocker = [];
The variable jadesLocker
now points to a completely different address in memory, as we have re-assigned it. johnsLocker
will still maintain the same data as before.
āPassing by referenceā
In some programming languages, when you pass an argument in to a function by reference, it means that if you modify that value directly, it will effect the implementation of it outside the scope of that function. In JavaScript however, there is no notion of āpassing by referenceā.
When passing any variable in to a function, effectively, a copy is made. When it is a value type, the actual value of the variable is duplicated. When it is a reference type, the address
is duplicated.
What this means is if you re-assign an array inside a function, it will not modify the external implementation of that variable, as it is working with a copy.
let initialArr = [1, 2, 3];
function modifyArrayThenLog(arrayArg) {
arrayArg = [4, 5, 6];
console.log(arrayArg); // [4, 5, 6]
console.log(initialArr); // [1, 2, 3];
}
modifyArrayThenLog(initialArr);
However, because our argument is still holding the value of the address, if we change the internals of the array, we will be modifying the data held at the memory location, and not the variable holding the memory location itself. So, if we do:
let firstArr = [1, 2, 3];
function modifyArrayThenLog(arrayArg) {
arrayArg[0] = 1000;
console.log(arrayArg); // [1000, 2, 3]
console.log(firstArr); // [1000, 2, 3]
}
modifyArrayThenLog(firstArr);
They will both log the same, because they are both still pointing to the same location in memory. Knowing this, we can use this to our advantage and not worry about returning arrays that we modify inside our functions. Be wary of this however, as it may lead to unintended consequences and hard-to-find bugs if used too often.