39

I have researched on net about the benefits of immutablejs over Object.freeze() but didn't find anything satisfying!

My question is why I should use this library and work with non native data structures when I can freeze a plain old javascript object?

alisabzevari
  • 8,008
  • 6
  • 43
  • 67
  • 1
    `Object.freeze()` is good for wrapping and training your brain with the concept of immutable states while using traditional data structures, but this approach will not bring the performance advantages of what immutablejs brings. – Yuya Apr 19 '16 at 18:46
  • 2
    Object.freeze() does not do any deep freezing, you can use https://github.com/substack/deep-freeze for that. The problem is that updating freezed object become tedious, especially for nested object, e.g., Object.assign({}, freezedObj, { a: 1, b: Object.assign({}, freezedObj.b, { c: 2 }) }); You may check out https://github.com/engineforce/ImmutableAssign, which is a lightweight immutable helper that can simply above assignment. – engineforce Jun 23 '16 at 11:42

6 Answers6

67

I don't think you understood what immutablejs offers. It's not a library which just turns your objects immutable, it's a library around working with immutable values.

Without simply repeating their docs and mission statement, I'll state two things it provides:

  1. Types. They implemented (immutable) infinite ranges, stacks, ordered sets, lists, ...

  2. All of their types are implemented as Persistent Data Structures.

I lied, here's a quote of their mission statement:

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

I urge you to read the articles and videos they link to and more about Persistent Data Structures (since they're the thing immutablejs is about), but I'll summarise in a sentence or so:

Let's imagine you're writing a game and you have a player which sits on a 2d plane. Here, for instance, is Bob:

var player = {
  name: 'Bob',
  favouriteColor: 'moldy mustard',

  x: 4,
  y: 10
};

Since you drank the FP koolaid you want to freeze the player (brrr! hope Bob got a sweater):

var player = Object.freeze({
    name: 'Bob',
    ...
});

And now enter your game loop. On every tick the player's position is changed. We can't just update the player object since it's frozen, so we copy it over:

function movePlayer(player, newX, newY) {
    return Object.freeze(Object.assign({}, player, { x: newX, y: newY }));
}

That's fine and dandy, but notice how much useless copying we're making: On every tick, we create a new object, iterate over one of our objects and then assign some new values on top of them. On every tick, on every one of your objects. That's quite a mouthful.

Immutable wraps this up for you:

var player = Immutable.Map({
    name: 'Bob',
    ...
});

function movePlayer(player, newX, newY) {
    return player.set('x', newX).set('y', newY);
}

And through the ノ*✧゚ magic ✧゚*ヽ of persistent data structures they promise to do the least amount of operations possible.

There is also the difference of mindsets. When working with "a plain old [frozen] javascript object" the default actions on the part of everything is to assume mutability, and you have to work the extra mile to achieve meaningful immutability (that's to say immutability which acknowledges that state exists). That's part of the reason freeze exists: When you try to do otherwise, things panic. With Immutablejs immutability is, of course, the default assumption and it has a nice API on top of it.

That's not to say all's pink and rosy with cherry on top. Of course, everything has its downsides, and you shouldn't cram Immutable everywhere just because you can. Sometimes, just freezeing an object is Good Enough. Heck, most of the time that's more than enough. It's a useful library which has its niche, just don't get carried away with the hype.

Zirak
  • 38,920
  • 13
  • 81
  • 92
  • 1
    Excellent answer. Also note that although in THEORY, structural sharing makes things more efficient, immutableJS has to carry the weight of its own infrastructure code, all of it not always easily optimizable by JS engines. What that means is that immutableJS will sometimes be much slower, use more memory and produce similar GC pressure. What's more, you could use a lib that only freeze objects in developement mode and leave them as is in production mode for even more efficiency. – AlexG Jan 15 '17 at 17:14
  • "And through the ノ*✧゚ magic ✧゚*ヽ of persistent data structures they promise to do the least amount of operations possible." Immutable datastructures tend to be more expensive, not less expensive. Compare the priority queue in Okasaki to the normal implementation, if you need an easy to understand example (alternately, heap, treap, etc.) – John Haugeland Apr 11 '17 at 22:39
  • 3
    Bit of syntactic sugar for the `Object.freeze` approach: `Object.freeze({...player, x: newX, y: newY});` – rcd Nov 11 '17 at 15:55
  • "player.set('x', newX).set('y', newY); And through the ノ*✧゚ magic ✧゚*ヽ of persistent data structures they promise to do the least amount of operations possible." Those calls cannot coalesce, so actually it is definitely doing _more_ work than rckd's approach – roberto tomás Oct 25 '19 at 17:42
  • Please notice that you just broke `object binding` by using `string literal`s to interact with the object. Therefore if you rename `x` to `eks` but forget to update `player.set('x', ...)` no IDE will ever scream at you. Stringly typed APIs are plague. – atoth May 10 '20 at 15:31
8

According to my benchmarks, immutable.js is optimized for write operations, faster than Object.assign(), however, it is slower for read operations. So the descision depends the type of your application and its read/write ratio. Following are the summary of the benchmarks results:

-- Mutable
Total elapsed = 103 ms = 50 ms (read) + 53 ms (write).

-- Immutable (Object.assign)
Total elapsed = 2199 ms = 50 ms (read) + 2149 ms (write).

-- Immutable (immutable.js)
Total elapsed = 1690 ms = 638 ms (read) + 1052 ms (write).

-- Immutable (seamless-immutable)
Total elapsed = 91333 ms = 31 ms (read) + 91302 ms (write).

-- Immutable (immutable-assign (created by me))
Total elapsed = 2223 ms = 50 ms (read) + 2173 ms (write).

Ideally, you should profile your application before introducing any performance optimization, however, immutability is one of those design decision must be decided early. When you start using immutable.js, you need to use it throughout your entire application to get the performance benefits, because interop with plain JS objects using fromJS() and toJS() is very costly.

PS: Just found out that deep freeze'ed array (1000 elements) become very slow to update, about 50 times slower, therefore you should only use deep freeze in development mode only. Benchmarks results:

-- Immutable (Object.assign) + deep freeze
Total elapsed = 45903 ms = 96 ms (read) + 45807 ms (write).
engineforce
  • 2,840
  • 1
  • 23
  • 17
3

Both of them don't make the object deeply immutable.

However, using Object.freeze you'll have to create the new instances of the object / array by yourself, and they won't have structural sharing. So every change which will require deeply copying everything, and the old collection will be garbage collected.

immutablejs on the other hand will manage the collections, and when something changes, the new instance will use the parts of the old instance that haven't changed, so less copying and garbage collecting.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209
3

There are a couple of major differences between Object.freeze() and immutable.js.

Let's address the performance cost first. Object.freeze() is shallow. It will make the object immutable, but the nested properties and methods inside said object can still be mutated. The Object.freeze() documentation addresses this and even goes on to provide a "deepFreeze" function, which is even more costly in terms of performance. Immutable.js on the other hand will make the object as a whole (nested properties, method, etc) immutable at a lower cost.

Additionally should you ever need to clone an immutable variable Object.freeze() will force you to create an entirely new variable, while Immutable.js can reuse the existing immutable variable to create the clone more efficiently. Here's an interesting quote about this from this article:

"Immutable methods like .set() can be more efficient than cloning because they let the new object reference data in the old object: only the changed properties differ. This way you can save memory and performance versus constantly deep-cloning everything."

In a nutshell, Immutable.js makes logical connections between the old and new immutable variables, thus improving the performance of cloning and the space frozen variables take in memory. Object.freeze() sadly does not - every time you clone a new variable from a frozen object you basically write all the data anew, and there is no logical connection between the two immutable variables even if (for some odd reason) they hold identical data.

So in terms of performance, especially if you constantly make use of immutable variables in your program, Immutable.js is a great choice. However, performance is not everything and there are some big caveats to using Immutable.js. Immutable.js uses it's own data structure, which makes debugging, or even just logging data to the console, a royal pain. It also might lead to a loss of basic JavaScript functionality (for example, you cannot use ES6 de-structuring with it) The Immutable.js documentation is infamously impossible to understand (because it was originally written for use only within Facebook itself), requiring a lot of web-searching even when simple issues arise.

I hope this covers the most important aspects of both approaches and helps you decide which will work best for you.

Nadav
  • 1,055
  • 1
  • 10
  • 23
  • Please properly distinguish between immutable variables (`const`) and immutable values (primitives, frozen objects, Immutable instances) – Bergi Feb 15 '18 at 13:12
  • 1
    Your first paragraph doesn't really make sense. How can Immutable.js traverse a nested object more efficiently than a deepfreeze function? – Bergi Feb 15 '18 at 13:13
  • objects and arrays are not primitive data types. As such declaring them with `const` will not make them immutable so using `const` for freezing them is not an option (the `const` documentation explains this quite nicely). You should also read my first paragraph again. I never wrote that Immutable.js traverses them more efficiently, I wrote that it is more efficient than "deepFreeze()" at deep freezing. – Nadav Feb 16 '18 at 02:15
  • I didn't mean to imply that they were, I was just suggesting that you don't use the term "variable" when you actually mean "object". – Bergi Feb 16 '18 at 02:22
  • But immutable.js doesn't exactly `freeze` them, so can you please explain what it actually does when you say "deep freeze" or "make immutable" and how that is more efficient? – Bergi Feb 16 '18 at 02:23
  • An object is a variable, and this question is about which method is better at creating immutable variables. – Nadav Feb 16 '18 at 02:36
  • No, an object is not a variable. Just consider two different variables holding the same (object) value… – Bergi Feb 16 '18 at 03:21
1

Object.freeze does not do any deep freezing natively, I believe that immutable.js does.

The same with any library -- why use underscore, jquery, etc etc.

People like re-using the wheels that other people built :-)

Tuvia
  • 859
  • 4
  • 15
  • Writing a deepFreeze is fairly easy: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze – alisabzevari Apr 19 '16 at 18:10
  • 5
    My problem with immutablejs is that you have to use its own data structures like `Map` and `List`. When you use these structures as in your business objects, you had made the whole code dependent to a library. – alisabzevari Apr 19 '16 at 18:50
1

The biggest reason that comes to mind - outside of having a functional api that helps with immutable updates, is the structural sharing utilized by Immutable.js. If you have an application that needs enforced immutability (ie, you're using Redux) then if you're only using Object.freeze then you're going to be making a copy for every 'mutation'. This isn't really efficient over time, since this will lead to GC thrasing. With Immutable.js, you get structural sharing baked in (as opposed to having to implement an object pool/a structural sharing model of your own) since the data structures returned from immutable are Tries. This means that all mutations are still referenced within the data structure, so GC thrashing is kept to a minimum. More about this is on Immutable.js's docsite (and a great video going into more depth by the creator, Lee Byron):

https://facebook.github.io/immutable-js/