Value Types vs Reference types in JavaScript Explained
Published: January 17, 2021When storing a variable in JavaScript, the JavaScript engine may store it as one of two types of values: a primitive or reference value. Understanding the difference in how they behave will help to avoid mistakes while manipulating them.
An Example
Instead of jumping straight into a boring explaination, consider this script:
const firstList = ['A', 'B', 'C'];
const secondList = firstList;
secondList.push('D');
console.log('firstList:', firstList);
console.log('secondList:', secondList);
We may expect the output to be:
"firstList:" ['A', 'B', 'C'];
"secondList:" ['A', 'B', 'C', 'D'];
But instead, we get an output of:
"firstList:" ['A', 'B', 'C', 'D'];
"secondList:" ['A', 'B', 'C', 'D'];
What Happened
This is because of how JavaScript treats arrays in memory. Arrays are stored as a reference value, so JavaScript will only copy the reference to that point in memory. This means that to the original array and not the value of the array.
Diving deeper, when accessing the varible as a primitive value, you are manipulating the actual value stored of that variable. In other words, the variable that is assigned a primitive value is accessed by value.
Unlike storing a primitive value, when you create an object, you are accessing the reference of that object in memory, rather than the actual value of that object. In other words, it means a variable that is assigned an object is accessed by reference.
Fixing our Example
The spread operator will create a new reference value in memory.
const firstList = ['A', 'B', 'C'];
const secondList = [...firstList];
secondList.push('D');
console.log('firstList:', firstList); // "firstList:" ['A', 'B', 'C'];
console.log('secondList:', secondList); // "secondList:" ['A', 'B', 'C', 'D'];
In ES5, you could copy an array into a new reference value with the slice method.
const firstList = ['A', 'B', 'C'];
const secondList = firstList.slice();
secondList.push('D');
console.log('firstList:', firstList); // "firstList:" ['A', 'B', 'C'];
console.log('secondList:', secondList); // "secondList:" ['A', 'B', 'C', 'D'];
However, the slice method cannot be used to create sparse arrays or arrays with 'holes' in them.
let array = [];
array[2] = 2;
array[4] = 4;
console.log(array.slice()); // [empty × 2, 2, empty, 4]
console.log(...array); // undefined undefined 2 undefined 4
Diving Deeper
Javascript has six data types that values are assigned as primative type: Boolean, null, undefined, String, Number, and Symbol. Like mentioned above, the values in primative types are accessed only by value, so they can be copied by value. The values can be copied and changed with no relation to each other.
let a = true;
let b = 'hello';
let x = a;
let y = b;
console.log(x, y, a, b); // true, 'hello', true, 'hello'
x = null;
y = undefined;
console.log(x, y, a, b); // null, undefined, true, 'hello'
Arrays, Functions, and Objects are all derived from object contructors in JavaScript. An explaination for this can be found here. The important thing to understand is that objects are passed by reference value. Like mentioned above, a varible assigned an object only knows is the location of the object in memory, not the object itself. Here is an example of a common bug that occurs when working with objects:
let vehicleOne = {
seats: 2,
airConditioning: true
};
let vehicleTwo = vehicleOne;
vehicleOne.color = 'red';
console.log(vehicleOne.color); // 'red'
console.log(vehicleTwo.color); // 'red'
vehicleTwo = {...vehicleOne};
vehicleOne.color = 'white';
console.log(vehicleOne.color); // 'white'
console.log(vehicleTwo.color); // 'red'
By using the '=' operator, we only assigned vehicleTwo the reference to the object, so any changes made to the vehicleOne object will alter the same spot in memory that vehicleTwo is assigned. Once again, we used the spread operator to copy and create a new reference value for vehicleTwo to avoid this error. This is why objects are known to be mutable in JavaScript.
This is also why you need to treat state in React as immutable, because the reference value will not change if you try and manipulate the state directly. You can read more about treating state as being immutable in React here.
Deep Copying
Both times we used the spread operator, it managed to create us a reference value for a new object. However, there is a catch. Consider this script:
const bookOne = {
title: 'Primative Types vs Reference Values in JavaScript',
pages: 50,
info: {
name: 'Joseph',
publication: 'dev.to'
}
}
const bookTwo = { ...bookOne }
bookTwo.title = 'Immutability in JavaScript';
bookTwo.info.publication = 'medium.com';
console.log('Book One title:', bookOne.title); // 'Primative Types vs Reference Values in JavaScript'
console.log('Book One authors:', bookOne.info.publication); // 'dev.to'
console.log('Book Two:', bookTwo.title); // 'Immutability in JavaScript'
console.log('Book Two:', bookTwo.info.publication); // 'dev.to'
Only the title of bookTwo changed. If one of the elements is another reference value to another object, all it would do is make a copy of the reference value into memory and it will not change what it’s referenced to. Here is a great article for further reading into how to deep copy objects in JavaScript.
If you like it, share it!