07. Immutable and Mutable Values in Javascript
We all know that there is a concept with immutable and mutable values in javascript. But what does it mean? How do we encounter it in programming and what are the points that we should remember when working with them? Those are the questions that will be answered in this tutorial in deep.
What is immutability
Immutable data is a concept which is used in languages widely, which means not changing the original declared value when modifying the reference value.
Example:
let str = "this is a string"; str = str.replace('this', 'hello');
When the strings are mutable values and references should link like following
But when strings are immutable it behaves like this
Strings are immutable in Javascript. Let’s see what are the other values which are immutable and mutable in Javascript.
Immutable values in Javascript
The following are considered immutable in Javascript, which we term as primitive values/literals.
- Reserved keywords
- Booleans
- Numbers
- Strings
Reserved keywords such as undefined
, null
cannot be modified and it makes no sense to modify them as well.
So as Numbers and Booleans. Booleans have only two values true and false which cannot be modified. And for numbers rather than modifying assigning a new value is quite optimistic.
When it comes to strings it can be a bit tricky to understand how they are immutable. As in example 1, the strings are immutable. The following examples will give you more understanding of immutable strings.
let str = "Hello"; let anotherStr = str.toUpperCase(); console.log(str); // Hello console.log(anotherStr); // HELLO let subStr = str.substring(0, 2); console.log(str); // Hello console.log(subStr); // He
This means the value represented by str
is not changed and it cannot be changed in the javascript.
let str = "Hello"; let he = 'He'; let subStr = str.substring(0, 2); // He console.log(he === subStr); // true
This is mapped in memory like the following. (Not the exact way but in high level)
This way of string sharing is known as String Interning and javascript does it by sharing strings to optimize memory.
Mutable values in Javascript
Mainly objects and arrays are known as mutable values in javascript. This means the original value will be modified when you do a change.
const value = {a: 'a', b: 'b'}; value.a = 'c'; console.log(value); // {a: 'c', b: 'a'};
Same with the arrays.
const array = ['1', '2', '3']; array[0] = 1; console.log(array); // [1, '2', '3'];
Objects are not compared with the value, they are rather compared with the reference to the value.
For Example:
const user1 = { age: 24 }; const user2 = { age: 24 }; const arr1 = []; const arr2 = []; console.log(user1 === user2); // false console.log(user1 == user2); // false console.log(arr1 === arr2); // false console.log(arr1 == arr2); // false
Because of this behavior, objects and values are known as Reference Types.
Reference types are compared if the declared reference points to the same value when comparing.
const user1 = { age: 24 }; const user2 = user1; user1.age = 32; console.log(user2.age); // 32 console.log(user1 === user2); // true
Equality of reference values or objects and arrays
Since the objects are mutable, it is not straight forward to check the equality of them. For that we need to check each attribute and see whether the objects are equal. This leads to other issues like comparing the prototype of the object correctly.
A deep comparison to find the equal object can be found here: https://github.com/ReactiveSets/toubkal/blob/master/lib/util/value_equals.js
You can shallowly compare an object easily with the following.
For example:
const shallowCompare = (obj1, obj2) => { const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); return obj1Keys.length === obj2Keys.length && obj1Keys.every(key => obj1[key] === obj2[key]); } shallowCompare({}, {}); // true shallowCompare({}, {b: 'c'}); // false shallowCompare({a:'a'}, {b: 'c'}); // false shallowCompare({a:'a'}, {a: 'a'}); // true
For a quick and easy solution, you can use lodash
isEqual
function if you are using the library in your codebase. But it will add an overhead of library size to your bundle.
Copy or clone objects and arrays
To copy an array you will have to copy the elements from one to another.
Array shallow copy example:
const a = ['a', 'b', 'c']; const b = a; const c = [...a]; const d = a.slice();
Object shallow copy example:
const obj = {x: 1, y: 2, z: 'Hello'}; // using spread operator const clone1 = {...obj}; // using Object assign const clone2 = Object.assign({}, obj); // using JSON parse and stringify, this will deep copy but has issues // not a good solution to use const clone3 = JSON.parse(JSON.stringify(obj)); // proposed native clone method // should be available in latest browsers and nodejs 11 onwards // may not work in older versions of browsers const clone4 = structuredClone(obj);
If you need to clone in a more robust way you can use lodash
clone
and cloneDeep
methods. This will add an overhead to the library size.