📜 ⬆️ ⬇️

JavaScript immutability

Immutability is the basic principle of functional programming, which also has much to offer object-oriented programs. In this article, I will tell you about what exactly is the cornerstone of immutability, how to use this concept in JavaScript, and why it is useful.

What is immutability?


The book definition of changeability reads as follows: "the tendency of an object to change or transform." In programming, we use this word when we mean objects whose state can be changed over time. The immutable value is exactly the opposite; it will never change after creation.

If this seems odd, let me remind you that most of the values ​​that we use all the time are actually immutable:
var statement = "I am an immutable value"; var otherStr = statement.slice(8, 17); 

I think that no one will be surprised if he finds out that the second line in no way changes the string value of the statement . In reality, there are no string methods that would modify the string on which they perform the action, they all return new strings. The reason is that strings are immutable - they cannot be converted; we can only create new strings.

Strings are not the only immutable values ​​embedded in JavaScript. Numbers are also immutable. In general, can you imagine an environment where the calculation of the expression 2 + 3 changes the value of the number 2 ? It sounds absurd, although we do it all the time with our objects and arrays.
')

In JavaScript, variability is abundant


In JavaScript, strings and numbers are designed to be immutable. However, consider the following example using arrays:
 var arr = []; var v2 = arr.push(2); 

What is the value of v2 ? If the arrays behaved according to strings and numbers, then v2 would contain a new array with one element inside - 2. However, this is a different case. Instead, the link arr was changed to contain a number, and v2 contains a new length arr .

Imagine the ImmutableArray type. His behavior, borrowing from numbers and strings, would look like this:
 var arr = new ImmutableArray([1, 2, 3, 4]); var v2 = arr.push(5); arr.toArray(); // [1, 2, 3, 4] v2.toArray(); // [1, 2, 3, 4, 5] 

Similarly, an immutable associative array, which could be used instead of most objects, would have methods for “setting” properties that would not actually set anything, but would return a new object with the required changes:
 var person = new ImmutableMap({name: "Chris", age: 32}); var olderPerson = person.set("age", 33); person.toObject(); // {name: "Chris", age: 32} olderPerson.toObject(); // {name: "Chris", age: 33} 

Just as 2 + 3 does not change the values ​​of either 2 or 3, the celebration by someone of their 33rd birthday does not cancel the truth that a person was 32 years old.

JavaScript immutability in practice


JavaScript (for now) does not have immutable lists and associative arrays, so now we need a third-party library. There are 2 very good libraries available. The first one is Mori , which allows you to use persistent data structures from ClojureScript, as well as JavaScript support APIs. The second is immutable.js , written by developers from Facebook. In this demonstration, I will use immutable.js for the simple reason that its API is more familiar to JavaScript developers.

In this demonstration, we will consider the principle of working with immutable data in Minesweeper. The board is represented by an immutable associative array, in which tiles are the most interesting part of the data. This is an immutable list of immutable associative arrays, where each of the latter (i.e. assoc. Masses - approx. Lane ) represents a separate tile on the board. The entire construct is initialized using JavaScript objects and arrays, and then becomes “immortal” thanks to the fromJS function from immutable.js:
 function createGame(options) { return Immutable.fromJS({ cols: options.cols, rows: options.rows, tiles: initTiles(options.rows, options.cols, options.mines) }); } 

The rest of the core of the game logic is implemented as functions that take this immutable structure as their first argument and return a new instance. The most important function is revealTile . When called, she marks the tile as open to open it. With a variable data structure, it will be very simple:
 function revealTile(game, tile) { game.tiles[tile].isRevealed = true; } 

But with immutable structures like those suggested above, it becomes more than difficult:
 function revealTile(game, tile) { var updatedTile = game.get('tiles').get(tile).set('isRevealed', true); var updatedTiles = game.get('tiles').set(tile, updatedTile); return game.set('tiles', updatedTiles); } 

Fe! Fortunately, such things are not uncommon. Therefore, in our toolkit there is a method for such purposes:
 function revealTile(game, tile) { return game.setIn(['tiles', tile, 'isRevealed'], true); } 

The revealTile function now returns a new immutable instance in which one of the tiles is different from the previous version. setIn is null-stable and is filled with empty objects if any of the parts of the key do not exist. In the case of the Minesweeper board, this is not desirable, since the missing tile means that we are trying to open the tile outside the board. This can be mitigated by using getIn to search for a tile before performing actions on it:
 function revealTile(game, tile) { return game.getIn(['tiles', tile]) ? game.setIn(['tiles', tile, 'isRevealed'], true) : game; } 

If the tile does not exist, then we simply return the existing game. It was a brief acquaintance with immutability in practice, if you want to understand more carefully, go to this codepen , it contains the full implementation of the rules of the game Minesweeper.

And what about performance?


You might think that this will have a significant performance degradation and in some ways you will be right. Every time you add something to an immutable object, we need to create a new instance by copying the existing values ​​and adding a new value to it. This will definitely lead to more memory load, as well as to greater computational costs than would be required for the mutation of an individual object.

Since immutable objects never change, they can be implemented using a strategy called “structural sharing”, which generates much less memory costs than you would expect. Compared to embedded arrays and objects, costs will still exist, but they will have a fixed value and can usually be compensated for by other benefits that are available due to immutability. In practice, in many cases the use of immutable data will increase the overall performance of your application, even if certain operations become more costly in isolation.

Improved change tracking


In any UI framework, one of the most difficult tasks is to search for mutations. This is such a well-known test that EcmaScript 7 provides a separate API to help track object mutations with better performance: Object.observe () . While some people like this API, others feel that this is not the answer to that question. In any case, it does not solve the problem of tracking mutations properly:
 var tiles = [{id: 0, isRevealed: false}, {id: 1, isRevealed: true}]; Object.observe(tiles, function () { /* ... */ }); tiles[0].id = 2; 

The mutation of the tiles [0] object does not activate our mutation observer, therefore, the proposed mutation tracking mechanism is not suitable even for a trivial application case. How can immutability help in this situation? Suppose that an application has state a , and a potentially new application has state b :
 if (a === b) { //   ,  } 

If the state of the application has not changed, then this is the same instance as before and we do not need to do anything at all. This definitely requires that we track the state-containing link, but the whole problem now comes down to the fact that we need to manage a single link.

findings


I hope that in this article you have gained some knowledge of how immutability will help you improve your code, and that a continued example can shed light on the practical aspects of working in this direction. Immutability is gaining popularity and this will not be the last article on this topic that you will read this year. Try it, and I promise that you will like it very quickly as much as you liked me.

Source: https://habr.com/ru/post/260319/


All Articles