Tutorial

Mutable Immutable JavaScript

Published on April 2, 2020
author

Jack Misteli

Mutable Immutable JavaScript

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

When I first dived into JavaScript and programming; I never really thought about immutable data. I would say animal is panda, then animal is lion.

var animal = 'panda';
animal = 'lion';

I was free to do whatever I wanted with my data! But… things changed… I grew up. People started telling me: “you should always use const if you can”. So I obediently did. But I didn’t really understand why.

Why Use Immutable Data

Because sometimes code changes things you don’t want to be changed. That’s a very lame answer I know, let me show you with an example.

Let’s say we have an e-commerce site.

Module: checkout.js
// First we import a function called validate address to check if our users entered a valid address
import validateAddress from 'address-validator'

const checkout = (user, cart) => {
 //... checkout code

 var userAddress = user.address
 // Checking if the address is valid
 const validAddress = validateAddress(userAddress);

 // If the address is valid then
 if (validAddress) {
  //... proceed to checkout
 }
}

Let’s say we got our address-validator by installing an npm package.

$ npm install address-validator

Everything works as expected but one day a new version is released a new line of code was introduced to the package which looks like this:

Module: validateAddress.js
const validateAddress = (address) => {
 address = '123 My Street, Bring Me Free Goods, USA';
 return true;
}

Now the variable userAddress will always be equal to the value of address! You can see how this is a problem.

This specific issue can be solved by using immutability. But it can also be solved with proper scoping. Try to figure out how!

Of course, malicious code is an edge case. But there are many ways in which immutable data can help you write better code. For example, a very common mistake is to accidentally mutate the property of an object.

Module: accidental-change.js
const userJack = {name: 'Jack Misteli'};
// I want to make a copy of user to do stuff with it
const user2 = userJack
user2.name = 'Bob'

// Because we didn't do a copy:
// userJack.name === 'bob'

This type of mistake can occur very often.

Immutability Tools

The most intuitive immutability tool is to use const.

const animal = 'panda';

// This will throw a TypeError!
panda = 'lion';

const is great. However, it sometimes only gives the illusion of immutability!

Module: example-checkout.js
const user = {
 name: 'Jack Misteli',
 address: '233 Paradise Road',
 bankingInfo: 'You wish!'
};

const maliciousAddressValidator = (user) => {
 user.address = 'Malicious Road';
 return true;
};

const validAddress = maliciousAddressValidator(user);
// Now user.address === 'Malicious Road' !!

There are different ways to solve this problem and introducing immutability is one of them.

First we can use the Object.freeze method.

const user = {
 address: '233 Paradise Road'
};
Object.freeze(user)
// Uing the same dodgy validateUserAddress
const validAddress = maliciousAddressValidator(user);
// Now user.address === '233 Paradise Road' !!

One issue with Object.freeze is that you don’t affect sub-properties. To reach all sub-properties you can write something like:

const superFreeze = (obj) => {
 Object.values(obj).forEach(val =>{
  if (typeof val === 'object')
    superFreeze(val)
  })
  Object.freeze(obj)
}

Another solution is to use proxies if you need flexibility.

Using Property Descriptors

In many sites, you will see that you can modify property descriptors to create immutable properties. And that’s true, but you have to make sure that configurable and writeable are set to false.

// Setting up a normal getter
const user = {
 get address(){ return '233 Paradise Road' },
};
console.log(Object.getOwnPropertyDescriptor(user, 'address'))
//{
//  get: [Function: get address],
//  set: undefined,
//  enumerable: true,
//  configurable: true
//}

const validAddress = maliciousAddressValidator(user);

// It looks like our data is immutable!
// user.address ===  '233 Paradise Road'

But the data is still mutable, it is just more difficult to mutate it:

const maliciousAddressValidator = (user) => {
 // We don't reassign the value of address directly
 // We reconfigure the address property
  Object.defineProperty(user, "address", {
    get: function() {
     return 'Malicious Road';
   },
 });
};
const validAddress = maliciousAddressValidator(user);
// user.address === 'Malicious Road'

Arr!

If we set writable and configurable to false we get:

const user = {};
Object.defineProperty(user, "address", {value: 'Paradise Road', writeable: false, configurable: false});


const isValid = maliciousAddressValidator(user)
// This will throw:
// TypeError: Cannot redefine property: address

Immutable Arrays

Arrays have the same problem as Objects.

const arr = ['a','b', 'c']
arr[0] = 10
// arr === [ 10, 'b', 'c' ]

Well you can use the same tools we used above.

Object.freeze(arr)
arr[0] = 10
// arr[0] === 'a'

const zoo = []
Object.defineProperty(zoo, 0, {value: 'panda', writeable: false, configurable: false});
Object.defineProperty(zoo, 1, {value: 'lion', writeable: false, configurable: false});
// zoo === ['panda', 'lion']

zoo[0] = 'alligator'
// The value of zoo[0] hasn't changed
// zoo[0] === 'panda

Other Methods

There are other tricks to keep your data safe. I highly recommend checking out our article about Proxy Traps to find out other ways to make your data immutable. We only scratched the surface here.

In this post, we explored options to introduce immutability without changing the structure and style of our code. In future posts, we will explore libraries such as Immutable.js, Immer or Ramda to right immutable code.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products


About the authors
Default avatar
Jack Misteli

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Hey, the example showing the value of address to ‘123 My Street, Bring Me Free Goods, USA’ seems incorrect.

Take a look at this snippet.

const validateAddress = (address) => {
  address = '123 My Street, Bring Me Free Goods, USA';
  return true;
 }

const checkout = (user) => {
  var userAddress = user.address
  validateAddress(userAddress);
  console.log(userAddress) // print 'one' instead of '123 My Street, Bring Me Free Goods, USA'
 }

 const user = {
  address: 'one'
 }

 checkout(user);

Same result if the user.address is a object instead of a string.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel