By reference and by value in JavaScript

In JavaScript, as in other programming languages, we often have to manipulate all types of data during our development. However, in JavaScript we have this notion of passing/copying by value or by reference, which can make things a little confusing when it comes to manipulating arrays or objects. We will try to understand this notion by looking more closely at the different types of data, what is a copy by reference and by value.

Data types

In JavaScript, there are 7 types of data.

  • Number
  • String
  • Boolean
  • Symbol
  • Object
  • undefined
  • null

It is important to note that among the data types mentioned above, there are primitive or simple data types and non-primitive, complex or object data types. Let’s see a little more what they are and what is the difference between these two types?

Primitive data types

A primitive is a data which is not an object and has no method. In JavaScript, the primitive data types are : Number, String, Boolean, Symbol, undefined and null.

A primitive value is represented directly at the lowest level in the implementation of the language, its value in memory is its real value. These data types are also immutable, which means that once created, they cannot be modified.

These data types are said to be passed, copied by value.

// Number
const number1 = 1;
const number2 = Number.parseInt("1.2"); // or Number.parseFloat("1.2");
const number3 = Number(123);
const number4 = +"123"; // or "123"*1

// String
const string1 = "toto";
const string2 = 'toto';
const string3 = `toto`;
const string4 = 1 + "toto"

// Boolean
const boolean1 = !true;
const boolean2 = true && false;
const boolean3 = false || true;
const boolean4 = 123 === "123";

// Symbol
const symbol = Symbol("xyz");

// undefined
const undefinedVar = undefined; // or simply undefined without assigning it

// null
const nullVar = null;

However typeof null returns an object type. Also the comparison between null and undefined with strict equality (or triple equal => type and value) using === is not the same as weak equality (or double equal => value) using ==

typeof null // "object"

null == undefined; // true
undefined == null; // true
undefined === null; // false

Non-primitive data types

An object is a value stored in memory which is referred to by means of an identifier. In JavaScript, the non-primitive data types or objects are: Object, Array, Function. These data types are said to be passed, copied by reference

// Object
const object = {
    id: 1,
    title: "My todo",
    description: "Do something",
    status: "pending"
}

// Array
const array = ["foo", "bar", "toto"];

// Function
const sayHello = function() {
    console.log("Hello!");
}

Here typeof array returns an object type

Pass or copy by value

Pass or copy by value means a copy of the original value. The copy has no reference to the original. Any change to the copied value will have no impact on the original value.

let status = "pending";

let newStatus = status;

console.log(newStatus);
//=> "pending"

newStatus = "done";

console.log(newStatus);
//=> "done"

console.log(status);
//=> "pending"

let a = 1;
let b = "todo";

function myFunction(a, b) {
  a = a + 2;
  b = "my " + b;
  
  console.log(a, b);
}

myFunction(a, b);
//=> 3 "my todo"

console.log(a, b);
//=> 1 "todo"

Pass or copy by reference

Pass or copy by reference means creating a reference to the original. The two will necessarily be linked. Any changes that occur on the copy will also affect the original.

const todo = {
    id: 1,
    title: "My first todo",
    description: "Do something",
    status: "pending"
}

// We create a copy of todo
const copyOfTodo = todo;

// We update the isDone property of copyOfTodo
copyOfTodo.status = "done";

console.log(copyOfTodo)
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/
console.log(todo)
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

const a = {
    id: 1,
    title: "My first todo",
    description: "Do something",
    status: "pending"
}

function myFunction(a) {
  const b = a;

  b.status = "done";
  
  console.log(a, b);
}

myFunction(a, b);
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

console.log(a);
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

A few mistakes to avoid

Two objects containing the same keys/values will not be strictly equal, since this equality implies the reference of each object which is not the same. For two objects to be strictly equal, they must have a reference to the same object.

It is also possible to compare them after performing a deepClone (real copy of the object, the two objects are not dependent on each other), a shallowClone (both objects are attached to the same memory block), or JSON.parse(JSON.stringify(original)) which can be used as a last resort but is not necessarily the right solution. I usually use deepClone to be sure for sure.

Also, the arrays, dates and functions will not be strictly equal.

We will see in a future article the different methods to clone an object, the use cases and how to compare certain types of data.

Let’s summarize

JavaScript always does a pass/copy by value for primitive data types and a âss/copy by reference for non-primitive data types.

It is necessary to avoid as much as possible when it is not necessary to modify the objects passed by reference (copy or pass to a function) in order to avoid side effects which functional programming emphasizes (pure functions, immutability…) that we will discuss in other articles as well.

Before you leave…
Thanks for reading! 😊