Immutable.js 初识

文章博客地址:http://pinggod.com/2016/Immutable/

Immutable.js 所建立的数据有一个诱人的特征:数据建立后不会被转变。我们运用 Immutable.js 的示例来诠释这一特征:

var Immutable = require('immutable');

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);

map1.get('b'); // 2
map2.get('b'); // 50

在上面代码第三行中,map1 运用 set 要领更新数据,效果返回一个新的 Map 范例数据 map2,map2 包括了更新后的数据,然则 map1 没有发生变化。这类特征让我们在援用数据的时刻毫无后顾之忧,因为任何对数据的修正都不会影响最原始的数据。在 Immutable.js 降生之前,我们能够运用深拷贝的体式格局模仿这一特征,然则会消耗过量的内存空间和盘算力。Immutable.js 比拟深拷贝的上风在于辨别发生变化的数据和未变化的数据,关于上面的 map1 和 map2,b 是变化的数据,所以 map1 和 map2 各保留一份 b 数据,而 ac 是未变化的数据,所以 map1 和 map2 依然同享 ac 的数据。

概览

Immutable Data 勉励开辟者运用纯函数式的开辟体式格局,并从函数式开辟中引入了惰性盘算的特征。虽然加入了许多函数式的观点,Immutable.js 依然供应了相似原生 JavaScript Array、Map 和 Set 中的要领,而且供应了在原生 JavasScript 数据和 Immutable 数据之间疾速转换的机制。

Immutable.js 的 API 重要包括以下几部份:

  • formJS(),将 JavaScript Object 和 Array 完全转换为 Immutable Map 和 List

  • is(),与 Object.is() 相似都是对值的比较,但它会将 Immutable Iterable 视为值范例数据而不是援用范例数据,假如两个 Immutable Iterable 的值相称,则返回 true。与 Object.is() 差别的是,is(0, -0) 的效果为 true

  • List,有序索引集,相似于 JavaScript 中的 Array

  • Map,无序 Iterable,读写 Key 的复杂度为 O(log32 N)

  • OrderedMap,有序 Map,排序依据是数据的 set() 操纵

  • Set,元素为举世无双的鸠合,增加数据和推断数据是不是存在的复杂度为 O(log32 N)

  • OrderedSet,有序 Set,排序依据是数据的 add 操纵。

  • Stack,有序鸠合,且运用 unshift(v)shift() 举行增加和删除操纵的复杂度为 O(1)

  • Range(),返回一个 Seq.Indexed 范例的数据鸠合,该要领吸收三个参数 (start = 1, end = infinity, step = 1),离别示意起始点、停止点和步长,假如 start 即是 end,则返回空的数据连系

  • Repeat(),返回一个 Seq.indexed 范例的数据连系,该要领吸收两个参数 (value,times),value 示意重复天生的值,times 示意重复天生的次数,假如没有指定 times,则示意天生的 Seq 包括无穷个 value

  • Record,用于衍生新的 Record 类,进而天生 Record 实例。Record 实例相似于 JavaScript 中的 Object 实例,但只吸收特定的字符串作为 key,且具有默许值

  • Seq,序列(may not be backed by a concrete data structure)

  • Iterable,能够被迭代的 (Key, Value) 键值对鸠合,是 Immutable.js 中其他一切鸠合的基类,为其他一切鸠合供应了 基本的 Iterable 操纵函数(比方 map()filter

  • Collection,建立 Immutable 数据结构的最基本的抽象类,不能直接组织该范例

1. fromJS()

Immutable.fromJS({a: {b: [10, 20, 30]}, c: 40}, function (key, value) {
    var isIndexed = Immutable.Iterable.isIndexed(value);
    return isIndexed ? value.toList() : value.toOrderedMap();
});
// true, "b", {b: [10, 20, 30]}
// false, "a", {a: {b: [10, 20, 30]}, c: 40}
// false, "", {"": {a: {b: [10, 20, 30]}, c: 40}}

fromJS() 的运用体式格局相似于 JSON.parse(),吸收两个参数:json 数据和 reviver 函数。

2. List

List<T>(): List<T>
List<T>(iter: Iterable.Indexed<T>): List<T>
List<T>(iter: Iterable.Set<T>): List<T>
List<K, V>(iter: Iterable.Keyed<K, V>): List<any>
List<T>(array: Array<T>): List<T>
List<T>(iterator: Iterator<T>): List<T>
List<T>(iterable: Object): List<T>

List() 是一个组织要领,能够用于建立新的 List 数据范例,上面代码演示了该组织要领吸收的参数范例,另外 List 具有两个静态要领:

  • List.isList(value),推断 value 是不是是 List 范例

  • List.of(...values),建立包括 ...values 的列表

下面演示几个 List 经常使用的操纵,更细致的 API 申明请参考官方文档:

// 1. 检察 List 长度
const $arr1 = List([1, 2, 3]);
$arr1.size
// => 3

// 2. 增加或替代 List 实例中的元素
// set(index: number, value: T)
// 将 index 位置的元素替代为 value,纵然索引越界也是平安的
const $arr2 = $arr1.set(-1, 0);
// => [1, 2, 0]
const $arr3 = $arr1.set(4, 0);
// => [ 1, 2, 3, undefined, 0 ]

// 3. 删除 List 实例中的元素
// delete(index: number)
// 删除 index 位置的元素
const $arr4 = $arr1.delete(1);
// => [ 1, 3 ]

// 4. 向 List 插进去元素
// insert(index: number, value: T)
// 向 index 位置插进去 value
const $arr5 = $arr1.insert(1, 1.5);
// => [ 1, 1.5, 2, 3 ]

// 5. 清空 List
// clear()
const $arr6 = $arr1.clear();
// => []

3. Map

Map 能够运用任何范例的数据作为 Key 值,并运用 Immutable.is() 要领来比较两个 Key 值是不是相称:

Map().set(List.of(1), 'listofone').get(List.of(1));
// => 'listofone'

然则运用 JavaScript 中的援用范例数据(对象、数组)作为 Key 值时,虽然偶然两个 Key 很像,但它们也是两个差别的 Key 值:

console.log(Map().set({}, 1).get({}))
// => undefined

Map() 是 Map 范例的组织要领,行动相似于 List(),用于建立新的 Map 实例,另外,还包括两个静态要领:Map.isMap() 和 Map.of()。下面引见几个 Map 实例的经常使用操纵,更细致的 API 运用申明请参考官方文档:

// 1. Map 实例的大小
const $map1 = Map({ a: 1 });
$map1.size
// => 1

// 2. 增加或替代 Map 实例中的元素
// set(key: K, value: V)
const $map2 = $map1.set('a', 2);
// => Map { "a": 2 }

// 3. 删除元素
// delete(key: K)
const $map3 = $map1.delete('a');
// => Map {}

// 4. 清空 Map 实例
const $map4 = $map1.clear();
// => Map {}

// 5. 更新 Map 元素
// update(updater: (value: Map<K, V>) => Map<K, V>)
// update(key: K, updater: (value: V) => V)
// update(key: K, notSetValue: V, updater: (value: V) => V)
const $map5 = $map1.update('a', () => (2))
// => Map { "a": 2 }

// 6. 兼并 Map 实例
const $map6 = Map({ b: 2 });
$map1.merge($map6);
// => Map { "a": 1, "b": 2 }

OrderedMap 是 Map 的变体,它除了具有 Map 的特征外,还具有递次性,当开辟者遍历 OrderedMap 的实例时,遍历递次为该实例中元素的声明、增加递次。

4. Set

Set 和 ES6 中的 Set 相似,都是没有重复值的鸠合,OrderedSet 是 Set 的遍历,能够保证遍历的递次性。

// 1. 建立 Set 实例
const $set1 = Set([1, 2, 3]);
// => Set { 1, 2, 3 }

// 2. 增加元素
const $set2 = $set1.add(1).add(4);
// => Set { 1, 2, 3, 4 }

// 3. 删除元素
const $set3 = $set1.delete(3);
// => Set { 1, 2 }

// 4. 并集
const $set4 = Set([2, 3, 4, 5, 6]);
$set1.union($set1);
// => Set { 1, 2, 3, 4, 5, 6 }

// 5. 交集
$set1.intersect($set4);
// => Set { 3, 2 }

// 6. 差集
$set1.subtract($set4);
// => Set { 1 }

5. Stack

Stack 是基于 Signle-Linked List 完成的可索引鸠合,运用 unshift(v)shift() 实行增加和删除元素的复杂度为 O(1)

// 1. 建立 Stack 实例
const $stack1 = Stack([1, 2, 3]);
// => Stack [ 1, 2, 3 ]

// 2. 取第一个元素
$stack1.peek()
// => 1

// 2. 取恣意位置元素
$stack1.get(2)
// => 3

// 3. 推断是不是存在
$stack1.has(10)
// => false

6. Range() 和 Repeat()

Range(start?, end?, step?) 吸收三个可选参数,运用要领以下:

// 1. 不传参
Range();
// => Range [ 0...Infinity ]

// 2. 设置 start 出发点
Range(10);
// => Range [ 10...Infinity ]

// 3. 设置 start 出发点和 end 尽头
Range(10, 20);
// => Range [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]

// 4. 设置 start 出发点、end 尽头和 step 步长
Range(10, 20, 3);
// => Range [ 10, 13, 16, 19 ]

Repeat(value, times?) 吸收两个参数,个中 times 重复次数是可选参数:

Repeat('foo');
// => Repeat [ foo Infinity times ]

Repeat('foo', 3);
// => Repeat [ foo 3 times ]

相似 Range()Repeat(value) 如许天生无穷长度鸠合的操纵,内部都存在惰性盘算的机制,只要着实取值时才会天生相应的效果。运用 ES6 中的 Generator 函数,能够轻松完成一个惰性盘算:

function* bigArr() {
    for (let i = 0; i < 100000; i++) {
        console.log(`bigArr(${i}): ${i}`)
        yield i;
    }
}

const arr = bigArr();

for (let i = 0; i < 10; i++) {
    console.log(arr.next());
}
// bigArr(0): 0
// => { value: 0, done: false }
// => bigArr(1): 1
// => { value: 1, done: false }
// => bigArr(2): 2
// => { value: 2, done: false }
// => bigArr(3): 3
// => { value: 3, done: false }
// => bigArr(4): 4
// => { value: 4, done: false }
// => bigArr(5): 5
// => { value: 5, done: false }
// => bigArr(6): 6
// => { value: 6, done: false }
// => bigArr(7): 7
// => { value: 7, done: false }
// => bigArr(8): 8
// => { value: 8, done: false }
// => bigArr(9): 9
// => { value: 9, done: false }

7. Record

Record 在表现上相似于 ES6 中的 Class,但在某些细节上还有所差别。经由历程 Record() 能够建立一个新的 Record 类,运用该类能够建立详细的 Record 实例,该实例包括在 Record() 组织函数中声明的一切属性和默许值。假如 Record 实例中的某个属性被删除了,则只会讲实例中的属性值恢复为默许值:

// 1. 建立 Record 实例
const A = Record({ a: 1, b: 2 });
const r = new A({ a: 3 });
// => Record { "a": 3, "b": 2 }

// 2. 删除实例属性
const rr = r.remove('a');
// => Record { "a": 1, "b": 2 }

另外,Record 实例还具有扩大性:

class ABRecord extends Record({a:1,b:2}) {
  getAB() {
    return this.a + this.b;
  }
}

var myRecord = new ABRecord({b: 3})
myRecord.getAB()
// => 4

8. Seq

Seq 有两个特性:immutable,一旦建立就不能被修正;lazy,惰性求值。鄙人面的代码中,虽然组合了多种遍历操纵,但实际上并不会有任何的求值操纵,只是地道的声明一个 Seq:

var oddSquares = Immutable.Seq.of(1,2,3,4,5,6,7,8)
    .filter(x => x % 2)
    .map(x => x * x);

假如要从 oddSquares 中掏出索引为 1 的元素,则实行历程为:

console.log(oddSquares.get(1));

// filter(1)
// filter(2)
// filter(3)
// map(3)
// => 9

Seq() 是 Seq 的组织要领,它依据传入的参数范例,输出相应的 Seq 范例:

  • 输入 Seq,输出 Seq

  • 输入 Iterable,输出同范例的 Seq(Keyed, Indexed, Set)

  • 输入 Array-like,输出 Seq.Indexed

  • 输入附加 Iterator 的 Object,输出 Seq.Indexed

  • 输入 Iterator,输出 Seq。indexed

  • 输入 Object,输出 Seq.Keyed

默许情况下 Seq 的惰性盘算效果不会被缓存,比方鄙人面的代码中,因为每一个 join() 都邑遍历实行 map,所以 map 统共实行了六次:

var squares = Seq.of(1,2,3).map(x => x * x);
squares.join() + squares.join();

假如开辟者晓得 Seq 的效果会被重复用到,那末就能够运用 cacheResult() 将惰性盘算的效果保留到内存中:

var squares = Seq.of(1,2,3).map(x => x * x).cacheResult();
squares.join() + squares.join();

9. Iterable 和 Collection

Iterable 是键值对情势的鸠合,其实例能够实行遍历操纵,是 immutable.js 中其他数据范例的基类,一切扩大自 Iterable 的数据范例都能够运用 Iterable 所声明的要领,比方 map 和 filter 等。

Collection 是 Concrete Data Structure 的基类,运用该类时须要最少继承其子类中的一个:Collection.Keyed / Collection.Indexed / Collection.Set。

React

在 React 官方文档的《Advanced Performance》 一节中,专门对 React 的机能瓶颈、优化体式格局做了细致的剖析。当一个 React 组件的 props 和 state 发生变化时,React 会依据变化后的 props 和 state 建立一个新的 virtual DOM,然后比较新旧两个 vritual DOM 是不是一致,只要当二者差别时,React 才会将 virtual DOM 衬着着实的 DOM 结点,而对 React 举行机能优化的中心就是削减衬着着实 DOM 结点的频次,间接地指出开辟者应当正确推断 props 和 state 是不是真正发生了变化。

在比对新旧 vritual DOM 和衬着着实 DOM 前,React 为开辟者供应了 shouldComponentUpdate() 要领中缀接下来的比对和衬着操纵,默许情况下,该要领总会返回 true,假如它返回 false,则不实行比对和衬着操纵:

// 最简朴的完成:
shouldComponentUpdate (nextProps) {
    return this.props.value !== nextProps.value;
}

看起来挺简朴,着实不然。当我们须要比对的值是对象、数组等援用值时,就会出现题目:

// 假定 this.props.value 是 { foo: 'bar' }
// 假定 nextProps.value 是 { foo: 'bar' },
// 明显这二者援用的内存地址差别,但它们具有雷同的值,这类时刻不应当继承实行衬着
this.props.value !== nextProps.value; // true

假如数据是 Immutable Data 的话,那末数据发生变化就会天生新的对象,开辟者只须要搜检对象运用是不是发生变化即可:

var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar'  });
var y = x.set('foo', 'baz');
x === y; // false

处置惩罚这一题目的另一种体式格局是经由历程 setter 设置 flag 对脏数据举行搜检,但芜杂的代码是在让人头疼。

总结

Immutable.js 所供应的 Immutable Data 和 JavaScript 固有的 Mutable Data 各有上风,将来 ECAMScript 有能够制订一套原生的 Immutable Data 范例,在这之前,Immutable.js 是一个不错的挑选。之前已写文章熟习过 Lodash 这一东西库,Immutable 内部也封装了诸多经常使用的数据操纵函数,所以假如让我来挑选的话,在 React 手艺栈中我会更偏幸 Immutable。

参考资料
    原文作者:ping4god
    原文地址: https://segmentfault.com/a/1190000005920644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞