Immutability in JavaScript – Explained with Examples
[ad_1]
We often hear the terms: immutable and immutability. But what do they mean, and, as developers, why should we care?
Immutable basically means something that cannot be changed. In programming, immutable is used to describe a value that cannot be changed after it’s been set.
But, most programs require creating, updating, and deleting data. So why would we ever want to work with data that can’t be changed?
In this tutorial, we’ll look at immutability of primitives, arrays, and objects with JavaScript examples. And I’ll explain why immutability is important for programming.
You can also watch the associated video here:
And here is a JavaScript example (which you can also view on Stackblitz):
// Import stylesheets
import './style.css';
// Write JavaScript code!
const appDiv = document.getElementById('app');
appDiv.innerHTML = `<h1>Open the console to see results</h1>`;
class Person {
//_name = "Nee";
//_name = ["Nee", "Ra"];
_name = { first: "Nee", middle: "L" };
get name() {
return this._name;
}
set name(value) {
console.log('In setter', value);
this._name = value;
}
}
let p = new Person();
//p.name = "Ra"; // Setter executes
//p.name.push("Lee"); // Setter doesn't execute
//p.name = [...p.name, "Lee"]; // Setter executes
//p.name.middle = "Lee"; // Setter doesn't execute
p.name = { ...p.name, middle: "Lee" }; // Setter executes
Let’s start with primitives.
Primitives in JavaScript: Naturally Immutable
In JavaScript, primitives, like strings and numbers, are immutable by default. This means that once a primitive value is created, it can’t be changed. Wait a minute, you might think – I change primitive variable values all the time!
Well, it might seem like you’re modifying a value. But that’s not actually the case. Here’s an example.
let greet = "Hello";
greet += ", World";
console.log(greet);
The first line of this code creates the string Hello
and assigns it to the greet
variable. The second line appends , World
to that string. So it looks like we’re changing the greet
string. But JavaScript does not change the string. Rather, it creates a new string.
Let’s look at an illustration. Here we have a greet
variable assigned to the Hello
string.
When the code appends text, JavaScript creates a new string. It then assigns the greet
variable to this new string. The original Hello
string is not modified.
So strings and other primitives are immutable by default in JavaScript.
How about arrays?
JavaScript Arrays are Mutable
In JavaScript, arrays are mutable by default. This means that the array can be altered after the array is created. We can modify it “in place”, adding, removing, or changing elements.
Let’s look at an example.
let ages = [42, 22, 35];
ages.push(8);
console.log(ages);
The first line of code defines an array and assigns a variable to that array. But in JavaScript, the variable doesn’t store the array. It stores the memory address where the array resides, as illustrated in Figure 3 here:
In the second line of code in the prior example, we use the push
method to modify the original array. In this case, we add 8 to the end of the array. This is shown in Figure 4:
Notice that the memory address of the array doesn’t change, but the array itself does change. So the array value is mutable.
This mutability provides flexibility. But mutability can lead to unintended side-effects, especially in larger applications or those involving concurrent operations.
And mutability has issues. Say you have code in a setter that should execute when the array is changed. Or you’re working with a framework, such as Angular, that provides change detection. Or you’re using a state library, such as Redux, that requires immutability.
As we saw in this example our array is changed…but our ages
variable didn’t actually change, since it’s referencing the memory address. So the setter or change detection or state management might not be aware that the array was changed.
To avoid these pitfalls of mutability, we, as JavaScript developers, often use patterns or methods that do not alter the original array but instead return a new array. This embraces immutability.
How to Embrace Immutability with Arrays
Let’s look at an alternate example:
let ages = [42, 22, 35];
ages = [...ages, 8];
console.log(ages);
In this code, we start with the same array. But when we add an element, we use the JavaScript spread operator. The spread operator makes a copy of the existing array at a new address by “spreading” the existing array.
We then add the new element to that copy. We also reassign the ages
variable to the address of the new array (Figure 5).
Notice that the original array is not changed. By using the spread operator, we achieve immutability.
In addition to the spread operator, many of the array methods also create a new array and therefore treat an array as immutable. Other array methods modify the array in place and are therefore mutable. Here are some examples.
Map
creates a new array from the existing array, mapping each element using a function we provide. It leaves the original array unchanged. So it supports immutability.
ages.map(x => x + 1);
Push
modifies the original array in place, mutating the array.
ages.push(8);
Filter
creates a new array with items matching the defined criteria. It leaves the original array unchanged.
ages.filter(x => x > 21);
Sort
sorts the array elements in place, thereby mutating the array.
ages.sort();
Slice
creates a new array from a portion of an existing array. Here we copy the original array elements starting at index 1 through index 3 to a new array.
ages.slice(1, 3);
Splice
changes the contents of an array in place, adding, removing, or replacing existing elements. In this example, the code starts replacing elements at index 2, only replaces 1 element, and replaces the element with “18”.
ages.splice(2, 1, 18);
So even though by default arrays are mutable, we can use immutability techniques to better manage our arrays.
What about objects?
The Mutable Nature of JavaScript Objects
Objects in JavaScript are also mutable by default. We can add or delete properties and change property values “in place” after an object is created.
let p = {name:"Nee", age: 30};
p.age = 31;
console.log(p);
The first line of this code example declares a person object with name and age properties. When a variable is assigned to that object, the variable doesn’t store the object, but rather a memory address where the object resides.
The second line of code in the prior example modifies the value of an object property, changing the age. This modification directly alters the original person object.
Notice that the memory address of the object doesn’t change, but the object itself changes. This is similar to array mutability, and that makes sense because arrays in JavaScript are basically objects.
Mutability provides flexibility but can lead to complex bugs if not carefully managed.
And as with an array, mutability has issues. Say you have code in a setter that should execute when the object changes. Or you’re working with a framework, such as Angular, that provides change detection. Or you’re using a state library, such as Redux, that requires immutability.
In this example, our object changed but our p
variable didn’t actually change, since it’s referencing the memory address. So the setter or change detection or state management may not see that the object was changed.
It’s often better to handle objects in an immutable manner. JavaScript provides features to aid with immutable objects.
Immutability with Objects
Here is an alternate example:
let p = {name:"Nee", age: 30};
p = {...p, age: 31};
console.log(p);
We start with the same object. But instead of changing an object’s property value directly, we again use the spread operator. The spread operator makes a copy of the object by spreading it into a new object at a new address. We update the property in that new object. We then reassign the p
variable to the address of the new object.
Notice that the original object is not changed. By using the spread operator, we achieve immutability.
So we’ve seen how primitives are immutable by default. And that arrays and objects are mutable, but we can work with them in an immutable way.
Nice! But why do we care?
Why Is Immutability Important?
There are several reasons that immutability is important to our everyday coding.
- Once an immutable value is set, it isn’t changed. Rather a new value is created. This makes the value predictable and consistent throughout the code. So it aids in managing state throughout the application. Plus immutability is a key principle in state management frameworks, such as Redux.
- Code becomes simpler and less error-prone when data structures don’t change unexpectedly. This also simplifies debugging and maintenance.
- Embracing immutability is in line with functional programming principles, leading to fewer side effects and more predictable code.
Wrapping Up
Immutability is a fundamental concept in programming. An immutable value is a value that can not be changed after it has been created.
This concept is important to functional programming and state management. It’s a valuable concept, especially when dealing with concurrency and large, complex codebases.
To see these concepts with animations, check out the video here:
Or try my stackblitz link: https://stackblitz.com/edit/immutability-deborahk. Be sure to fork my project to try out your own changes.
While JavaScript objects and arrays are mutable by default, adopting an immutable approach to handling them can lead to cleaner, more reliable, and easier-to-maintain code.
[ad_2]
Source link