Mutation and Immutability in JavaScript

More about mutation in JavaScript and modifications on objects

Discussion

  • What are Mutation and Immutability?

  • Why Immutability.

  • Better tests and easy to debug.

  • Functional Programming approach and pure functions.

  • Deep copy v/s Shallow copy.

  • Ways to create copies of objects

What is Mutation and Immutability

Mutation in general refers to change in the structure or sequence of any object or entity. In JavaScript, we refer to consider an object is mutated if any fields of the object are modified in place.

let person = {
    name: "John",
    age: 28,
};
person.name = "Brian"; // Here we change name attribute in place.

In the above code snippet, we change the value of the name in the person object using the dot operator, We are not creating any new memory reference but we are just modifying the object using the same memory reference, This is an example of mutating an object.

Immutability refers to the state where an object or entity cannot be modified. Generally, attributes give the state of the object and functions give us the behaviour of the object. So Immutalility enables us to prevent changing the state of the object. We can make the objects immutable using Object.freeze(<object_name>) method.

Why do we need to make the objects Immutable

JavaScript is a functional programming language so we try to always write pure functions and always return a new object/state with modified data to prevent side effects. In our context changing the object may lead to unpredictable behaviour and bugs which are side effects to our function so we always return a new object instead of mutating the same object.

We are not locking the object but we share multiple copies of data to the other subscribers so there is no conflict in accessing the data.

function addFullName(user: {firstname: string, lastname: string}) {
    return {
        ...user,
        fullname: `${user.firstname} ${user.lastname}`,
    }; // using the object literal syntax make sure we always return new object
};

Better tests and Pure functions

This above code is a good example of a pure function. The output of the above function is solely dependent on the input arguments and it doesn't modify and external state or create any side effects. These pure functions are easy to be tested and their behaviour is easily predictable. We write these smaller atomic pure functions for better code maintenance and debug the issues easily.

Ways to create copies of objects

  • spread(...) operator - We can use the spread operator as shown in the above example to iterate over the key-value pairs in the object and place them inside the object literal to create a new object(copy)

  • Object.assign() - We can use a JavaScript inbuilt function Object.assign(target_object, source1, source2,...). It takes all the key-value pairs in the source objects provided and copies them inside the target object.

  • using JSON object we can use JSON.parse(JSON.stringify(<object>)) to create a copy of the object.

  • we also have inbuilt javascript functions - map, filter and reduce which are used on an array.

  • There are other libraries like loadash, immutable.js.. etc. to create deep clones of the objects.

  • This brings us to another topic deep copy vs. shallow copy

Deep copy v/s shallow copy

Let's take a look at the below code snippet

let person = {
    name: "John",
    address: {
        lane: "3-128/ st 2",
        country: "canada",
    }
}

let person2 = {
    ...person,
}
console.log(person.address === person2.address); //true

The output of the above code is true, why do you think this happened even when we have made 2 separate copies of the person? This is because whenever we make a copy of the object javascript only copies the 1st level key-values are copied and the next level key-values are referenced. This concept is called shallow copy.

The disadvantage of shallow copy is that it's against our immutability principle when we are working on a data-intensive application (tables and cells) where we are maintaining multiple levels of data in objects while copying the data when we refer to the key values in the new object happens we are accessing the old references and the old object is modified forever. This can lead to unexpected behaviour of our application.

The ... operator, assign() creates a shallow copy of the object. To make a deep copy we can iterate over each key-value pair in every level and copy them into a new object. Instead of doing this, we can use JSON.parse(JSON.stringify(<object>)), loadash.deepClone() or immutable.js library. Other ways can be explored.

conclusion

On a closing note, it's a good practice follow create and copy immutable objects and use pure functions in javascript language for better maintenance and understanding of the codebase.