Immutability in JavaScript — When and Why Should You Use It
In this article, take a look at immutability in JavaScript and see when and why you should use it.
Join the DZone community and get the full member experience.
Join For FreeThe concept of immutability is nothing new because it’s been around for a long time in functional and object-oriented programming languages. The idea wasn’t much prevalent in JavaScript, but it has slowly grown into the programming community recently. React is a strong proponent of keeping the state immutable and Facebook has contributed a library called Immutable.js to help this cause.
In this tutorial, we’ll answer the following questions:
- What is immutability?
- Why and when should you keep your state immutable?
- Issues pertaining to mutations
Mutable and Immutable Data Types
A mutable object is an object whose state can be modified or changed over time. An immutable object, on the other hand, is an object whose state cannot be modified after it is created. Well, that’s how the textbook defines mutable and immutable objects.
In JavaScript, string and numbers are immutable data types. If that feels strange, here is an example to demonstrate why we call them immutable.
var x =7;
x += 1;
Numbers, for instance, are immutable because you can’t change its value. For example, you can’t literarily change the value of 7 to 8. That doesn’t make sense. Instead, you can change the value stored in the variable x from 7 to 8.
Strings are also immutable too, and this is obvious when you try to change a particular character in a string.
xxxxxxxxxx
var myString = "I am immutable"
myString[2] = 'c'
console.log(myString)
But that doesn’t stop you from creating new strings.
xxxxxxxxxx
var myString = "I am immutable.";
var newString = myString.slice(0,7);
Other string manipulation methods like trim
, concat
, etc. also return a new string. No string operations modify the string they operate on in JavaScript.
This is because strings and numbers are primitive value types that are assigned by value and not by reference. Here’s a good discussion on copy by value vs. copy by reference for starters.
You might also like: Do You Really Need Immutable Data?
Practical Considerations
When I was working on my product, Storylens — A free blogging platform, I’ve been using a combination of React and Redux. Both React and Redux give high consideration to shallow-equality checking. This ensures that the DOM rerenders only when an underlying object’s reference has changed. So when you update the state or the store in React/Redux, you’ll have to avoid mutating objects and arrays.
Similarly, most of the modern frontend stacks today rely heavily on Immutable data structures and if you’re going to use them, it might be a better idea to get used to these concepts. Let’s have a look at arrays and objects.
Arrays and Objects Are Mutable
Arrays and objects are not immutable in JavaScript because they can indeed change their value over time. Let’s verify this by running the following code examples in the console.
xxxxxxxxxx
var x = {
foo: 'bar'
};
var y = x;
x.foo = 'Something else';
console.log(y.foo); // Something else
console.log(x === y) // true
As you can see, x is a mutable object, and any change in the property of x gets reflected in the value of y. Why? They all share the same reference. So, when you update the value of a property of x, you’re modifying the value for all the references to that object.
Let’s have a look at Arrays:
xxxxxxxxxx
var x = ['foo'];
var y = x.push('bar')
It’s certain that the value of y is going to contain both ‘foo’ and ‘bar’. But what about x?
xxxxxxxxxx
console.log(x); // ['foo', 'bar']
console.log(x === y) // true
Both x and y are references to the same item, and the method push mutates the original array. A lot of the array methods that you use every day are Mutator methods. Here’s the list of Array methods that operate on the original array.
- copyWithin
- fill
- pop
- push
- reverse
- shift
- sort
- splice
- unshift
Methods like map and filter are immutable because they create a new array without mutating the original array. You can verify this by running an example like this:
xxxxxxxxxx
let a = [1,2,3,4,5]
let b = a.map(item => item-1)
console.log(a ==b) //false
console.log(a) //[1,2,3,4,5]
So, what does it mean to make objects and arrays immutable? Well, there are techniques in JavaScript that you can use to create new values without losing its previous versions. Whenever the content of an object needs to be modified, you won’t directly change the original value object. Instead, we will be treating it as immutable and return an entirely new object with the updated value.
Going Immutable in JavaScript
You have many options for enforcing immutability in JavaScript. Some of the ES6 features can be used for cloning values without using the original references. In this section, we’ll explore the various techniques for implementing Immutability in JavaScript for Arrays and Objects.
Objects
To avoid mutation, the option available in ES5 was freezing the object using Object.freeze. It wasn’t a great option either. With ES6, you can prevent mutating objects using Object.assign
method.
Object.assign() copies the values (of all enumerable own properties) from one or more source objects to a target object. It has a signature of Object.assign(target, …sources).
Let’s consider an example of objects:
xxxxxxxxxx
const person = {
name: 'Jim',
age: 19
}
const newPerson = Object.assign({}, person, {
age: 22
})
console.log(newPerson === person) // false
console.log(person) // { name: 'Jim', age: 19 }
console.log(newPerson) // { name: 'Jim', age: 22 }
The parameter to Object.assign()
method is the target object, and you can pass one more than one objects as sources. The method merges all the sources from right to left in that order and returns the target object. You can use the method for merging objects and cloning them shallowly.
Given its rather verbose syntax, Object.assign can make your code difficult to read. There’s an alternative syntax called object spread operator that uses three dots to shallow clone an object. The spread operator lets you use to copy enumerable properties from multiple sources using a succinct way. Here is how you clone objects using spread operator.
xxxxxxxxxx
const person = {
name: 'Jim',
age: 19
}
const newPerson = {
...person,
age: 22
}
console.log(newPerson === person) // false
console.log(newPerson) // { name: 'Jim', age: 22 }
Deleting Object Properties
There are multiple ways that you remove a property without mutating the object. My personal favorite is using the object destructuring syntax.
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Here is an example:
xxxxxxxxxx
const x = {
foo: 'bar',
far: 'boo',
faz: 'baz'
};
If you already know the name of the property to remove, you can try this:’
xxxxxxxxxx
const { foo, ...everythingElse } = x;
console.log(everythingElse);
// Will be { "far": "boo", "faz":"baz" }
If the name of the property to remove is dynamic, you can do this:
xxxxxxxxxx
const key = 'far';
const { [key]: value, ...everythingElse} = x;
console.log(everythingElse);
// Will be { "foo": "bar", "faz":"baz" }
Arrays
As mentioned earlier, most of the array methods are mutable. But you can use the spread operator syntax on arrays to add and remove items from an array. Let’s have a look at an example:
xxxxxxxxxx
const fruits = ['peach', 'pear', 'apple', 'plum']
const newFruits = [...fruits, 'orange']
console.log(fruits === newFruits) //false
Nice, that was easy! We kept the old array intact and created a new one. What about delete?
The splice()
method removes the item from the original array. So you could instead use filter()
which returns a new array.
Immutability in Array of objects and Nested Objects
Array functions like .map are immutable when you’re mapping an array of values. However, they’re not immutable when you’re working with an array of objects. Let’s expand on the fruits example that we created earlier.
xxxxxxxxxx
fruits= [
{
id: 1,
name: "Peach"
},
{
id: 2,
name: "Pear"
},
{
id:3,
name: "Apple"
}
]
const updatedFruits = fruits.map(
item => {
item.name = "Orange";
return item
}
)
xxxxxxxxxx
console.log(fruits === updatedFruits) // True
Why is the original array’s data mutated as well? The map method clones the original array as expected, but since we are operating on an array of objects, each item in the array is a reference to the object in memory. Any modification that you make to an object in the cloned array will modify the original object via reference.
Why Is Immutability Important?
If you’ve been actively using frontend libraries like React, Vue, or a state management library like Redux, you might have come across warnings that say you shouldn’t mutate the state. Keeping the state immutable can help you in terms of performance, predictivity and better mutation tracking.
Predictability
For any medium-sized application, there will be state — a lot of it — and asynchronous actions updating that state. The state of the app will be significantly different from the initial state after the end user starts using it. Mutation hides changes that result in side effects which in turn makes it hard to debug and find bugs. By keeping your structures immutable, you’ll be able to predict what’s in the state at any given time and you can rest assured that there won’t be any nasty side effects.
Tracking Mutations
WIth immutable entities, you can see the changes that happen to these objects as a chain of events. This is because the variables have new references which are easier to track compared to existing variables. This can particularly help you with debugging your code and building better concurrent applications. There are event debuggers that help you replay DOM events with video playbacks that works entirely based on tracking mutation.
Summary
So, in this article, we covered the basics of mutations and why the word immutability is popular in the JavaScript world so much lately. If you have any questions, let me know in the comments.
Further Reading
Opinions expressed by DZone contributors are their own.
Comments