Immutability in JavaScript

Immutability is a super hot subject in modern JavaScript and the reason for that is, of course, because of the functional programming paradigm.

Immutable data is tightly connected with a functional approach where any mutation is considered as an unwanted side effect. But first, let’s dive into details of mutability and immutability.

What is mutability

Just to keep things clear, there is nothing wrong if the code is mutable – the JavaScript’s Array API is mutable and there’s nothing wrong with that. Yet, the misuse of mutability can cause side effects to your software. Let’s take a look at the example code below:

const person = {
name: 'Rodrigo',
email: 'email@email.com'
}

// Make a copy of person object
const newPerson = person;

// Changing the email of the new person
newPerson.email = 'somethingelse@email.com';

console.log(newPerson === person); // true
console.log(person); // { name: 'Rodrigo', email: 'somethingelse@email.com' }
console.log(newPerson); // { name: 'Rodrigo', email: 'somethingelse@email.com' }

You can see that we’re copying the object (person) to another object (newPerson) and making a small change on newPerson. The problem here is that te change reflects in both objects.

This happens because when you assign an object to a variable in JS, you are actually assigning a memory reference to it, so when you do this:

const newPerson = person;

You are just copying that reference, not the real values. Both variables point to the same place.

Going immutable

Immutability is the art of maintaining the state of an object, making development simple, traceable, testable and decreasing any possible side effects. The main idea is: an update should not change the object, but create a new object with the updated data.

Instead of passing the object and mutating it, we will be better off creating a completely new object:

const person = {   name: 'Rodrigo',   email: 'email@email.com' } 
const newPerson = Object.assign({}, person, { email = 'somethingelse@email.com' })

console.log(newPerson === person); // false

console.log(person) // { name: 'Rodrigo', email: 'email@email.com' }
console.log(newPerson) // { name: 'Rodrigo', email: 'somethingelse@email.com' }

But hey, we are using ES6, can’t we do this in another way? Sure! We can use the Spread operator! Take a look:

const person = {   name: 'Rodrigo',   email: 'email@email.com' } 
const newPerson = { ...person, email: 'somethingelse@email.com' }

console.log(newPerson === person); // false - really different objects
console.log(person) // { name: 'Rodrigo', email: 'email@email.com' }
console.log(newPerson) // { name: 'Rodrigo', email: 'somethingelse@email.com' }

Neat right? Again, same result and even cleaner code. First, we create a new object assigning {} to a variable, then we use the ‘spread’ operator (…) to copy all the properties from person to the new object. Then we define a new ‘email’ property that overrides the old one. Note that in this case, order matters, if email: ‘somethingelse@email.com’ would be defined above …person, it would be overridden by the value of email from person object.

Arrays

First let’s do a little example of how you can mutate an array:

const fruits = [ 'Orange', 'Apple' ] 
const newFruits = characters newFruits.push('Banana')
console.log(fruits === newFruits) // true 🙁

Arrays work the same way as objects so you can also use the spread operator to go immutable on arrays. Here’s how you can use it:

const fruits = [ 'Orange', 'Apple' ] 
const newFruits = [ ...fruits, 'Banana' ]
console.log(fruits === newFruits) // false
console.log(fruits) // [ 'Orange', 'Apple' ]
console.log(newFruits) // [ 'Orange', 'Apple', 'Banana' ]

As you can see, you can achieve immutability pretty easily using just plain, modern Javascript! In the end it’s all about common sense and understanding what your code actually does. If you don’t program with care, Javascript can be unpredictable.

Leave a comment

Leave a Reply