Ideal JavaScript immutable data structure

Time:2020-9-17

1、 Introduction

Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.

Immer provides a more convenient way to operate immutable state

2、 Core advantages

hisConvenienceIt is mainly reflected in the following aspects:

  • There is only one (core) API:produce(currentState, producer: (draftState) => void): nextState
  • No additional data structure is introduced: there is no list, map, set and other custom data structures, so no special equality comparison method is required
  • Data manipulation is entirely type based: it’s intuitive to manipulate data with a pure native API

For example:

const myStructure = {
  a: [1, 2, 3],
  b: 0
};
const copy = produce(myStructure, () => {
  // nothings to do
});
const modified = produce(myStructure, myStructure => {
  myStructure.a.push(4);
  myStructure.b++;
});

copy === myStructure  // true
modified !== myStructure  // true
JSON.stringify(modified) === JSON.stringify({ a: [1, 2, 3, 4], b: 1 })  // true
JSON.stringify(myStructure) === JSON.stringify({ a: [1, 2, 3], b: 0 })  // true

Compared with the full set of data structure and operation API provided by immutable:

const { Map } = require('immutable');
const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 1000);
// New instance, leaving the original immutable.
updatedMap !== originalMap;
const anotherUpdatedMap = originalMap.set('b', 1000);
// Despite both the results of the same operation, each created a new reference.
anotherUpdatedMap !== updatedMap;
// However the two are value equal.
anotherUpdatedMap.equals(updatedMap);

Immer is too simple

3、 Implementation principle

Two key points: copy on write and proxy

Copy-on-write

concept

Copy-on-write (CoW or COW), sometimes referred to as implicit sharing or shadowing, is a resource-management technique used in computer programming to efficiently implement a “duplicate” or “copy” operation on modifiable resources.

Copy on write, also known as implicit sharing or shadowing, is a resource management technology in computer programming, which is used to efficiently copy or copy modifiable resources

If a resource is duplicated but not modified, it is not necessary to create a new resource; the resource can be shared between the copy and the original. Modifications must still create a copy, hence the technique: the copy operation is deferred to the first write. By sharing resources in this way, it is possible to significantly reduce the resource consumption of unmodified copies, while adding a small overhead to resource-modifying operations.

Specifically, if a resource is copied but there is no change, it is unnecessary to create the new resource. At this time, the replica can share the same resource with the original, and it is still necessary to create a replica when modifying. Therefore, the key lies in:Postpone the copy operation until the first write。 Sharing resources in this way can significantly reduce the resource consumption of the unmodified replica, but only slightly increase the cost of resource modification

application

The cow strategy is mainly applied in the following aspects:

  • Virtual memory management: process shared virtual memory, fork() system call, etc
  • Storage: logical volume management, file system, database snapshot
  • Programming languages: many data types in PHP, QT
  • Data structure: implement immutable data structure, such as state tree

Take fork() system call as an example

Ideal JavaScript immutable data structure

Through the cow mechanism to achieve the memory sharing between processes, copy on demand

Immer and copy on write

In immer, the copy on write mechanism is used to solve the performance burden caused by copying data structures, as shown in the following figure:

Ideal JavaScript immutable data structure

Copy the data structure only when the data changes (write), otherwise share the sameTherefore:

copy === myStructure  // true
modified !== myStructure  // true

Proxy

Proxy provides a way to hook native data operation API, such as:

const data = { a: 1 };
const proxy = new Proxy(data, {
  set(target, key, value, receiver) {
    console.log(`Set key = ${key}, value = ${value}`);
    return Reflect.set(target, key, value, receiver);
  }
});

proxy.a = 2;
//Output set key = a, value = 2
data.a === 2  // true

It can not only monitor data changes, but also allow operations to intercept and even redirect

const data = { a: 1 };
const copy = {};
const p = new Proxy(data, {
  set(target, key, value, receiver) {
    //Do not write back data
    // return Reflect.set(target, key, value, receiver);
    //It's all on copy
    Reflect.set(copy, key, value, copy);
  }
});

p.a = 2;
data.a === 1  // true
copy.a === 2  // true

What did you find?

dataIn this way, it becomes an immutable data structure

P. S. for more information about proxy syntax and application scenarios, see proxy_ ES6 note 9

Copy-on-write + Proxy

Back to the original example:

const modified = produce(myStructure, myStructure => {
  myStructure.a.push(4);
  myStructure.b++;
});

We tryMerge proxy and copy on write through magic

function produce(data, producer) {
  let copy;
  const copyOnWrite = value => {
    copy = Object.assign({}, value);
  };

  const proxy = new Proxy(data, {
    set(target, key, value, receiver) {
      //Copy on write
      !copy && copyOnWrite(data);
      //It's all on copy
      Reflect.set(copy, key, value, copy);
    }
  });
  producer(proxy);
  return copy || data;
}

P. Note that theproduceThe implementation is only used to explain the principle of immer, there are simple bugs, and it has no practical value

You get the core APIproduce

produce(currentState, producer: (draftState) => void): nextState

In immer,dataAboveproxyKnown as draft:

Ideal JavaScript immutable data structure

It is very vivid, and the revision on the draft (i.edraftStateThe modification will be copied according to the copy on write mechanism. The source data will not be affected, and the draft is completed (i.eproducerAfter that, patch the source data according to the draft to get the new data

Very clever design, just like the layer operation in Photoshop:

  • Open picture
  • Create a new layer and daub it on the new layer
  • flatten image

reference material

  • Copy-on-write
  • ZFS Administration, Part IX- Copy-on-write
  • Immer: Immutability the easy way

It’s good to have gains and doubts

Focus on the front end back WeChat official account, and you will get a series of “use”.heart“Original” high-quality technical articles, including but not limited to the front-end Node.js And server technology

This paper was first published in ayqy.net Link to the original text: http://www.ayqy.net/blog/immer/