The Different Levels of Immutability in Javascript

Avoiding mutations in our code is becoming increasingly important in Javascript due to the principles of functional that makes our code easier to predict and to scale. This blog post can be considered a continuation of a previous one: The Limits of Object.assign().

Setting Up the Environment

To begin with, we are going to create a folder to work called immutable-levels.

$ mkdir immutable-levels
$ cd immutable-levels

Because I am first and foremost an Angular 2 developer and I love the technology, we are going to be using typescript in our examples. Let’s go ahead and create a “package.json” file, install typescript as a dependency and create a npm script to run the transpiler in watch mode.

$ npm init -f
$ npm install typescript --save

package.json

{
  "name": "immutable-levels",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "tsc",
    "build:init": "tsc --init",
    "build:watch": "tsc -w"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^1.8.10"
  }
}

With all our commands in place, we can now create our file tsconfig.json.

$ npm run build:init

The final step of our setup is to create a file to work and fire up typescript in watch mode.

$ touch index.ts
$ npm run build:watch

Level 0: Mutable Object

Normally in javascript, an object is mutable unless otherwise stated. To prove that, we can write the following code:

// index.ts

let paul = {
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
};

paul = {
  age: 60,
  height: 165,
  address: {
    city: 'Montreal',
    street: 'Ste. Catherine'
  }
};

console.log(JSON.stringify(paul, null, 2));
$ node index.js

>>

{
  "age": 60,
  "height": 165,
  "address": {
    "city": "Montreal",
    "street": "Ste. Catherine"
  }
}

As expected, we were able to completely change the reference of our variable an assign a new object to it. Our object is by default mutable.

Level 1: The “const” Keyword

To prevent the ability to change the reference of our variable, we could use the “const” keyword instead of “let”.

// index.ts

const paul = {
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
};

paul = {
  age: 60,
  height: 165,
  address: {
    city: 'Montreal',
    street: 'Ste. Catherine'
  }
};

console.log(JSON.stringify(paul, null, 2));

Now, typescript is going to complain because changing the reference of a constant object is forbidden.

index.ts(10,1): error TS2450: Left-hand side of assignment expression cannot be a constant.

We have achieved the first level of immutability! Sadly, we can still mutate our object changing the properties directly.

// index.ts

const paul = {
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
};

paul.age = 60;
paul.height = 165;
paul.address.city = 'Montreal';
paul.address.street = 'Ste. Catherine';

console.log(JSON.stringify(paul, null, 2));

Now typescript is not complaining anymore and if we run this code, we can see that our object again has mutated.

$ node index.js

>>

{
  "age": 60,
  "height": 165,
  "address": {
    "city": "Montreal",
    "street": "Ste. Catherine"
  }
}

Still, not what we want.

Level 2: Object.freeze()

We can go one step further and not only prevent changing the reference of the variable but prevent modifying its properties using “Object.freeze()“.

// index.ts

const paul = {
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
};

Object.freeze(paul);

paul.age = 60;
paul.height = 165;
paul.address.city = 'Montreal';
paul.address.street = 'Ste. Catherine';

console.log(JSON.stringify(paul, null, 2));

This time, when we execute the code something curious happen.

$ node index.js

>>

{
  "age": 30,
  "height": 180,
  "address": {
    "city": "Montreal",
    "street": "Ste. Catherine"
  }
}

Even though we tried to modify the properties age and height, we can see that at the end those properties were not affected. The same cannot be said about the nested object address that mutated again. That happens because Object.freeze doesn’t prevent nested objects from being mutated. We still have some more work to do.

Level 3: Immutable.js

It’s clear that the language is not well suited to completely prevent mutations and that’s why we need to resort to libraries, in this case immutable.js created by Facebook. To use this package in typescript, we need to install the package and the typings as well.

$ npm install immutable --save
$ npm install typings --save

We need to add additional script commands to “package.json”

{
  "name": "immutable-levels",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "tsc",
    "build:init": "tsc --init",
    "build:watch": "tsc -w",
    "typings": "typings",
    "typings:init": "typings init",
    "typings:install": "typings install",
    "postinstall": "typings install"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^1.8.10",
    "typings": "^1.3.0"
  }
}

With this commands, we can now create the file tsconfig.json and install the typescript definition file of the immutable.js library.

$ npm run typings:init
$ npm run typings:install -- dt~immutable --save --global

We are finally ready to start using this library to prevent mutations of our object.

// index.ts

import { fromJS } from "immutable";

const paul = fromJS({
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
});

paul.age = 60;
paul.height = 165;
paul.address.city = 'Montreal';
paul.address.street = 'Ste. Catherine';

console.log(JSON.stringify(paul, null, 2));

If we try to run this code, we are going to get an exception when trying to modify the nested properties.

$ node index.js
> TypeError: Cannot set property 'city' of undefined

If we remove both lines that try to mutate the address object, we are going to see the same result as with Object.freeze().

// index.ts

import { fromJS } from "immutable";

const paul = fromJS({
  age: 30,
  height: 180,
  address: {
    city: 'Toronto',
    street: 'Markham'
  }
});

paul.age = 60;
paul.height = 165;

console.log(JSON.stringify(paul, null, 2));
$ node index.js

>> 

{
  "age": 30,
  "height": 180,
  "address": {
    "city": "Toronto",
    "street": "Markham"
  }
}

None of the properties changed. Complete immutability achieved!

2 thoughts on “The Different Levels of Immutability in Javascript”

  1. in the line “$ npm run typings:install — dt~immutable –save –global”: what does “dt~immutable”? what does “dt~”? 🙂

    1. That is searching for the type definition file of immutablejs in “definitely typed” instead of “npm”. You can fin the sources of the immutablejs package with “typings search immutable”.

So, what do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.