If you have been programming in JavaScript for a little while you'd have noticed that const
in JavaScript does not mean you can't change it. There's also no memory, performance or any other benefit. WTF!
Okay, okay. Look at this piece of code:
const toastStatus = "burnt"; // why though?
// let's change this
toastStatus = "not ok";
You will be expecting that it will give an error, right? Yup. You are right. It does give this error:
Uncaught TypeError: Assignment to constant variable.
So far so good. Let's say we want to give an id
to the toast status:
const toast = {
id: "da_best_toast",
status: "burnt"
};
And something happens and this toast suddenly becomes unburnt (accidentally of course):
toast.status = "all ok"
You'd be an expecting the same old error, right? But guess what? No error. Not even a hint. It's all okay (as the toast said). WTF! Same is the case with any Object
(i.e. Arrays, Objects, Classes, Functions etc. etc.)
WTF!!
Okay, okay. I guess that's a good enough rant for now. SO what's the problem?
The Problem
Nothing is immutable in JavaScript. Nothing is unchangable in JavaScript. Anything can become anything in JavaScript. That's why you have TypeScript but even then there are some quirks in JavaScript that will make you pull your hair out.
So what do we do if we want perfect immutability? Such that no one can change the object. At all?
You could use a third-party library like: Immutable-Js
or immer
. But why? Why, why, why? When JavaScript can do it but for some reason doesn't do it by default (via the const
keyword)?
If you haven't noticed by now: Objects are extensible in JavaScript. Meaning you can add, delete or edit without any restriction. Any Object. Even the standard ones like global
or window
. Now, I understand that that is a cool thing and all but it has many, many disadvantages. So let's try to make our objects...constant.
The Solution..s
Object.freeze
:
From MDN:
The Object.freeze() method freezes an object. A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties, and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.
I think you got the point. It makes your object unchangable. Forever.
Here's the same example as above:
const toast = Object.freeze({
id: "da_best_toast",
status: "burnt"
});
Now if you wanted to change the status like this:
toast.status = "all ok"
It will give this error (be a good kid and turn on strict mode
):
TypeError: Cannot assign to read only property 'done' of object '#<Object>'
And later if you accidentally add another property:
toast.bread = "baba bread";
It will give another error:
TypeError: Cannot add property bread, object is not extensible
And then if you try and delete the status
(accidentally, of course):
delete toast.status
You'd get:
TypeError: Cannot delete property 'done' of #<Object>
In this way you can make any Object immutable. You could also write a utility function like this, to make it a bit flashier:
function im(obj){
return Object.freeze(obj)
}
// and then use it like so:
const immutable = im({ data: "some_data" });
And if you wanted to check whether an object is frozen, simply do:
Object.isFrozen(toast); // === true
Now what if you wanted to only disallow new properties from being added/deleted but allow existing properties from being changed. In other words, what if you wanted "partial" immutability?
Well, for that you have:
Object.seal
:
From MDN:
The
Object.seal()
method seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable.
So basically, it will only allow existing properties from being edited (not deleted). Let's see an example.
const network = Object.seal({
stable: true,
speed: "50mbps",
status: "connected"
});
// let's suppose, the network is disconnected suddenly, you'd simply do:
network.status = "disconnected";
// however if you wanted to add upstream speed, it will throw:
network.upstream = "25mbps";
// or if you try and delete, it will also throw.
delete network.stable
You can easily check if an object is sealed or not using:
Object.isSealed(network); // === true
So far so good. But what if you wanted to disallow only additions but allow edits and deletions? For that you have Object.preventExtensions
.
Object.preventExtensions
From MDN:
The
Object.preventExtensions()
method prevents new properties from ever being added to an object (i.e. prevents future extensions to the object).
Let's see an example:
const person = Object.preventExtensions({
name: "Baker Yota",
age: 35,
});
// now let's say you wanted to add a new property, it will throw.
person.father = "Samhil Yota"
// But you can edit the age:
person.age = 36;
// And delete it too:
delete person.age;
And if you wanted to check whether an object is extensible or not, simply do:
Object.isExtensible(person); // === false
So there you have it: Immutability in JavaScript.
Notes:
- Objects that are manipulated in this way do not have their references altered. So
frozen_object === real_object
givestrue
. - You can put in any kind of
Object
. Be it anArray
,Function
,Class
etc.
Conclusion:
Javascript is a nice language but it does have some wacky areas a beginner would not be expecting. Hopefully, this little lesson in immutability will have made you understood that area of "science". Now don't be annoying and give me a reaction. :D Just joking. But seriously, do leave a comment with your thoughts.
Thanks for reading,
thecodrr
P.S. Here is my latest project in case you are interested:
thecodrr / fdir
⚡ The fastest directory crawler & globbing library for NodeJS. Crawls 1m files in < 1s
The Fastest Directory Crawler & Globber for NodeJS
fdir
in speed. It can easily crawl a directory containing 1 million files in < 1 second.
fdir
uses expressive Builder pattern to build the crawler increasing code readability.
fdir
only uses NodeJS fs
& path
modules.
fdir
supports all versions >= 6.
🖮 Hackable: Extending fdir
is extremely simple now that the new Builder API is here. Feel free to experiment around.
* picomatch
must be installed manually by the user to support globbing.
Support
…Do you like this project? Support me by donating, creating an issue, becoming a stargazer, or opening a pull request. Thanks.
Top comments (0)