1

Immutability ( Immutability ) is a core principle of functional programming, and it has a lot of applications in object-oriented programming. In this article, I'll show you what immutability is ( Immutability ), why she's still so badass, and how to apply it in JavaScript .

What is immutability ( Immutability )?

Let's take a look at the dogmatic definition of variability ( Mutability ): "liable or subject to change or alteration" Bar)". In programming, we use mutability ( Mutability ) to describe an object whose state can still be changed after it is created. Then when we say immutable ( Immutable ), it is the opposite of mutable ( Mutable ) - meaning, Once created, it can no longer be modified.

If what I said makes you feel weird again, forgive me for a little reminder, in fact, many things we usually use are actually immutable!

 var statement = 'I am an immutable value';
var otherStr = statement.slice(8, 17);

I guess no one will be surprised that statement.slice(8, 17) has not changed statement variables? In fact, none of the methods on the string object modify the original string , they all return the new string . The reason is simple, because string is immutable ( Immutable ) - they cannot be modified, what we can do is to get one based on the original string operation New string .

Note that string JavaScript not the only built-in immutable ( Immutable ) data type in ---8f34ad8fcddc592ee04f597d0eb9f371---. number is also immutable ( Immutable ). Otherwise, try to imagine this expression 2 + 3 , if the meaning of 2 can be modified, how should the code be written|_|. It sounds absurd, but we often do this to object and array in programming.

JavaScript is full of changes

In JavaScript , string and number are immutable from the beginning ( Immutable ) by design. However, take a look at the following example about array :

 var arr = [];
var v2 = arr.push(2);

Let me ask you, what is the value of v2 ? array 88141959977ee4f86ecf09d880ef912d---和stringnumber变( Immutable )的,那v2 is a new array containing a number 2 2ba1e8658c164b4d5b7984fd40434abf---. In fact, it really isn't like that.这里arr引用的array修改了,里面添了一个数字2 ,这时v2的值(也就是arr.push(2) The return value of arr.push(2) ) is actually arr the length at this time- is 1 .

Imagine we have an immutable array ( ImmutableArray ). Like string , number , she should be able to be used 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, there can also be an immutable Map ( ImmutableMap ), which can theoretically replace object in most scenarios, she should have a set method, But this set method doesn't stuff anything into the original Map , but returns a new Map containing the stuffed value:

 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}

Like 2 + 3 in this expression, we can't change the meaning of 2 or 3 which is celebrated in his person His 33rd birthday doesn't affect the fact that he was once 32.

JavaScript immutability ( Immutability ) combat

There are no immutable JavaScript list and map , so we still need the help of the third party library for now. There are two very good ones, one is Mori Facebook she brought the ClojureScript API to JavaScript ; Facebook produced immutable.js . In the following examples, I will use immutable.js because her API is more developer friendly to JavaScript .

In the following example, we use immutable ( Immutable ) knowledge to build a Minesweeper game. We use an immutable map to build the minesweeper game panel, of which the tiles (minefield block) part is worth paying attention to, it is an immutable map -Composed of immutable map list (Translator's Note: Started to circle again), each of which is immutable map represents a tile (mine block) .整个这个雷区面板都是由JavaScriptobject array ,最后由immutable.jsfromJS Do immutable processing:

 function createGame(options) {
  return Immutable.fromJS({
    cols: options.cols,
    rows: options.rows,
    tiles: initTiles(options.rows, options.cols, options.mines)
  });
}

The remaining main logic part is "mine sweeping", pass in the mine sweeping game object (an immutable structure) as the first parameter, and the one to be "sweeped" tile (mine block) object , and finally returns the new minesweeper game instance. Below we will talk about this revealTile function. When it is called, the state of tile (the mine block) is reset to the "swept" state. In the case of variable programming, the code is simple:

 function revealTile(game, tile) {
  game.tiles[tile].isRevealed = true;
}

Then look at the immutable data structure introduced above, and frankly, the code becomes a bit ugly at first:

 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);
}

I'm going, it's ugly and there is no wood!

Fortunately, immutability does not stop there, there must be salvation! This need is very common, so the tool has long considered it, and can do this:

 function revealTile(game, tile) {
  return game.setIn(['tiles', tile, 'isRevealed'], true);
}

revealTile返回一个新的实例了,新tile (雷区块)的isRevealedgame The example is different. The setIn used here is a function of null-safe (null value safe), any keyPath in key will create a new immutable at this location map It is not clear, the original work did not elaborate, so I will not say more, and those who are interested can come here to figure it out for themselves). This null-safe feature is not suitable for our current example of a minesweeper game, because "sweeping" a non-existent tile (mine block) means that we are trying to clear a place outside the minefield, That's clearly not right! Here we need to do one more check, check whether tile (mine block) exists through the getIn method, and then "scan" it:

 function revealTile(game, tile) {
  return game.getIn(['tiles', tile]) ?
    game.setIn(['tiles', tile, 'isRevealed'], true) :
    game;
}

If tile (mine block) does not exist, we return to the original minesweeper game instance. This is an exercise about immutability ( Immutability ) that can be quickly started. If you want to know more, you can see the codepen and click the preview. The complete implementation is inside.

How is the performance?

You might think, this fucking Performance should low explode, I can only say that in some cases you are right. Whenever you want to add something to an immutable ( Immutable ) object, it must first copy the existing value to the new instance, then add content to the new instance, and finally return the new instance. Compared with mutable objects, this will inevitably consume more memory and computation.

Because immutable ( Immutable ) objects never change, there is actually an implementation strategy called "struct sharing", which makes her memory consumption much less than you think. Although there is still additional overhead compared to the built-in "change" of array , object , this starts constant and can definitely be made immutable ( Immutability ) are eliminated and reduced by many other advantages brought by . In practice, the advantages of immutability ( Immutability ) can greatly optimize the overall performance of a program, even if some of the individual operations are more expensive.

Improve change tracking

Various UI frameworks, the hardest part is always change tracking. This is JavaScript a common problem in the community, so EcmaScript 7 provides a separate API that can be tracked on the premise that Performance changes : Object.observe() . Many people are excited about it, but many people think that this API is not the same. They believe that, in any case, this API does not solve the change tracking problem well:

 var tiles = [{id: 0, isRevealed: false}, {id: 1, isRevealed: true}];
Object.observe(tiles, function () { /* ... */ });

tiles[0].id = 2;

In the above example, the change of tiles[0] did not trigger observer , so in fact this proposal fails even the simplest change tracking. How is immutability ( Immutability ) solved? Suppose there is an application state a , and then its internal value is changed, so a new instance b is obtained:

 if (a === b) {
  // 数据没变,停止操作
}

a没有被修改,那b就是a ,它们指向同一个实例, ===做other things. Of course, this requires us to track the reference of the application state, but the complexity of the whole problem has been greatly simplified. Now we only need to judge whether they are references to the same instance. I really don't need to investigate whether the certain field in it has changed. .


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。