一.简介

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

Immer提供了一种更方便的不可变状态操作方式

二.核心优势

方便之处主要体现在:

  • 只有一个(核心)API:produce(currentState, producer: (draftState) => void): nextState

  • 不引入额外的数据结构:没有 List、Map、Set 等任何自定义数据结构,因此也不需要特殊的相等性比较方法

  • 数据操作完全基于类型:用纯原生 API 操作数据,符合直觉

例如:

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

比起Immutable提供的全套数据结构及其操作 API

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 显得太过简洁

三.实现原理

两个关键点:Copy-on-write 与 Proxy

Copy-on-write

概念

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,简称 CoW 或 COW),也叫隐式共享(implicit sharing)或隐藏(shadowing),是计算机编程中的一种资源管理技术,用于高效地复制或拷贝可修改资源

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.

具体的,如果复制了一个资源但没有改动,就没必要创建这个新的资源,此时副本能够与原版共享同一资源,在修改时仍需要创建副本。因此,关键在于:将拷贝操作推迟到第一次写入的时候。通过这种方式来共享资源,能够显著减少无改动副本的资源消耗,而只是略微增加了资源修改操作的开销

应用

COW 策略主要应用在以下几方面:

  • 虚拟内存管理:进程共享虚拟内存、fork()系统调用等

  • 存储:逻辑卷管理、文件系统、数据库快照

  • 编程语言:PHP、Qt 中的许多数据类型

  • 数据结构:实现不可变的数据结构,如状态树

以 fork()系统调用为例

通过 COW 机制来实现进程间的内存共享,按需拷贝

Immer 与 Copy-on-write

在 Immer 中,Copy-on-write 机制用来解决拷贝数据结构产生的性能负担,如下图:

只在数据发生改变(write)时才拷贝数据结构(copy),否则共享同一个,因此:

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

Proxy

Proxy 提供了一种 Hook 原生数据操作 API 的方式,例如:

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;
// 输出 Set key = a, value = 2
data.a === 2 // true

不仅能够监听到数据变化,还允许进行操作拦截、甚至重定向:

const data = { a: 1 };
const copy = {};
const p = new Proxy(data, {
set(target, key, value, receiver) {
// 不写回data
// return Reflect.set(target, key, value, receiver);
// 全都写到copy上
Reflect.set(copy, key, value, copy);
}
}); p.a = 2;
data.a === 1 // true
copy.a === 2 // true

发现了什么?

data就这样成为了不可变的数据结构

P.S.关于 Proxy 语法及应用场景的更多信息,见proxy(代理机制)_ES6 笔记 9

Copy-on-write + Proxy

回到最初的示例:

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

我们试着将 Proxy 与 Copy-on-write 通过魔法融为一体

function produce(data, producer) {
let copy;
const copyOnWrite = value => {
copy = Object.assign({}, value);
}; const proxy = new Proxy(data, {
set(target, key, value, receiver) {
// 写时复制
!copy && copyOnWrite(data);
// 全都写到copy上
Reflect.set(copy, key, value, copy);
}
});
producer(proxy);
return copy || data;
}

P.S.注意,这里提供的produce实现仅用来说明 Immer 原理,存在浅显的 bug,不具有实用价值

就得到了核心 API produce

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

在 Immer 中,data之上的proxy被称为 Draft(草稿):

非常形象,在草稿上的修改(即对draftState的修改,会按 Copy-on-write 机制拷贝)不影响源数据,草稿完成(即producer执行完毕)之后,按照草稿对源数据打补丁,得到新数据

很巧妙的设计,就像 Photoshop 中的图层操作:

  • 打开图片

  • 新建图层,在新图层上涂涂抹抹

  • 合并图层

参考资料

Copy-on-write + Proxy = ?的更多相关文章

  1. http-code 未译

    1xx Informational Request received, continuing process. This class of status code indicates a provis ...

  2. 利用grunt-contrib-connect和grunt-connect-proxy搭建前后端分离的开发环境

    前后端分离这个词一点都不新鲜,完全的前后端分离在岗位协作方面,前端不写任何后台,后台不写任何页面,双方通过接口传递数据完成软件的各个功能实现.此种情况下,前后端的项目都独立开发和独立部署,在开发期间有 ...

  3. RFC3261--sip

    本文转载自 http://www.ietf.org/rfc/rfc3261.txt 中文翻译可参考 http://wenku.baidu.com/view/3e59517b1711cc7931b716 ...

  4. [security][modsecurity][nginx] nginx 与 modsecurity

    参考文档: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#installation-for-nginx nginx不支 ...

  5. scrapy 常用代码

    一,scrapy请求 yield scrapy.Request(url=url, dont_filter=True, callback=self.page, meta={'item': item}) ...

  6. java动态代理_aop2

      一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模 ...

  7. Selenium - WebDriver Advanced Usage

    Explicit Waits # Python from selenium import webdriver from selenium.webdriver.common.by import By f ...

  8. windows下的asp.net core开发及docker下的发布

    参照下面,搭建好开发环境.Docker及配置好Docker加速器 http://www.cnblogs.com/windchen/p/6257846.html 参照下面,将windows共享目录挂载到 ...

  9. vue-cli3.0和element-ui及axios的安装使用

    一.利用vue-cli3快速搭建vue项目 Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统.有三个组件: CLI:@vue/cli 全局安装的 npm 包,提供了终端里的vue命令( ...

  10. Java开发中的23种设计模式详解(2)结构型

    我们接着讨论设计模式,上篇文章我讲完了5种创建型模式,这章开始,我将讲下7种结构型模式:适配器模式.装饰模式.代理模式.外观模式.桥接模式.组合模式.享元模式.其中对象的适配器模式是各种模式的起源,我 ...

随机推荐

  1. lb的keepalive问题

    lb的keepalive问题 0. keepalive 大家都很清楚他的用意了,就是为了减少3次握手,设置一个timeout,比如说20s ,在20s内不请求,连接还是保持着,这时候请求过来,不需要重 ...

  2. C#/VB.NET 将SVG图片添加到PDF、转换为PDF

    以下内容介绍在C# 程序中如何将SVG图片添加到PDF文档.以及如何将SVG图片转换为PDF文档. 一.环境准备 先下载PDF类库工具,Spire.PDF for .NET hotfix 6.5.6及 ...

  3. 抛弃os.path,拥抱pathlib

    基于Python的文件.目录和路径操作,我们一般使用的是os.path模块. pathlib是它的替代品,在os.path上的基础上进行了封装,实现了路径的对象化,api更加通俗,操作更便捷,更符编程 ...

  4. Maven 私服你应该不陌生吧,可你会用 Artifactory 搭建吗?

    JFrog Artifactory 是一个 Artifacts 仓库管理平台,它支持所有的主流打包格式.构建工具和持续集成(CI)服务器.它将所有二进制内容保存在一个单一位置并提供一个接口,这使得用户 ...

  5. PAT-1063 Set Similarity (set集合)

    1063. Set Similarity Given two sets of integers, the similarity of the sets is defined to be Nc/Nt*1 ...

  6. 11.Java连接Redis_Jedis_测试联通

    使用Java开发项目的时候使用Redis的话,目前有一些开源API可以使用. 最常用的就是jedis,它提供了许多基于Java的对象和方法来调用Redis的指令. jedis的jar包下载地址http ...

  7. vue采用history路由的服务器部署问题

    发现部署问题 在部署的时候发现打开的页面是空白 之前部署原理 之前的页面都是作为静态文件形式打包上传到服务器上 http://www.xiedashuaige.cn/bolg2.0/#/home 就和 ...

  8. protocbuf的简单理解

    之前通信协议替换为protocbuf!新老交替,很多不同看法,也提出来一些负面因数: 1.老的内部通信协议体已经有一段时间了,稳定熟悉! 2.通过通信结构体进行交互,实际上并没有序列化和反序列化的过程 ...

  9. Unity 游戏框架搭建 2019 (五十、五十一) 消息机制小结&MonoBehaviourSimplify 是框架?

    我们花了 5 篇文章学习了消息机制的方方面面.并且完成了一个简易消息机制,之后集成到了我们的 MonoBehaviourSimplify 里. 现在 MonoBehaviourSimplify 有一点 ...

  10. SD.Team主题形象小人偶

              W e ♥ S D     ♫ ♪ 咔咔咔~可能源码冲突会造成小人偶光头 :)