您现在的位置: 首页 > 微信小程序运营 > 经验 >

扩展微信小程序框架功能:Immutable.js

来源:微信小程序 编辑:Yiyongtong.com 发布时间:2017-11-14 17:19热度:

Immutable.js 是 Facebook 开发的不可变数据集合。Immutable Data(不可变数据)一旦创建就不能被修改。通过使用Immutable Data,可以更容易的处理缓存、回退、数据变化检测等问题,应用开发更简单。Immutable.jsJav ...

 
 
 

Immutable.js 是 Facebook 开发的不可变数据集合。Immutable Data(不可变数据)一旦创建就不能被修改。通过使用Immutable Data,可以更容易的处理缓存、回退、数据变化检测等问题,应用开发更简单。

Immutable.js

JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。

    var foo = { a: 1 };    var bar = foo;
    bar.a = 2;    console.log(foo === bar); // 输出:true
    console.log("foo.a=", foo.a, "bar.a=", bar.a);  // 输出:foo.a= 2 bar.a= 2

以上代码中, foo.a 也被改成了 2。虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。Immutable 可以很好地解决这些问题。

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

immutable.js是由facebook开源的一个项目,主要是为了解决javascript Immutable Data的问题。Immutable.js提供一个Lazy Sequence,允许高效的队列方法链,类似 map 和 filter ,不用创建中间代表。Immutable.js 通过惰性队列和哈希映射提供 Sequence, Range, Repeat, Map, OrderedMap, Set 和一个稀疏 Vector。

Immutable.js优点:

  • Immutable降低了 Mutable 带来的复杂度。
  • Immutable使用了 Structure Sharing 会尽量复用内存。没有被引用的对象会被垃圾回收。
  • 因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重做这种功能。
  • 使用了 Immutable 之后,数据天生是不可变的,不需要使用并发锁保证并发安全。
  • Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

Immutable.js提供了7种不可变的数据类型: List、Map、Stack、OrderedMap、Set、OrderedSet、Record。

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 数据结构的最基础的抽象类,不能直接构造该类型

Immutable.js使用

从 JavaScript 数据生成不可变对象

    var foo2 = Immutable.fromJS({ a: 1 });    var bar2 = foo2.setIn(['a'], 2);       
    console.log(foo2 === bar2);  // 输出:false
    console.log("foo2.a=", foo2.getIn(['a']), "bar2.a=", bar2.getIn(['a'])); // 输出:foo2.a= 1 bar2.a= 2

以上代码,使用 fromJS 将 Object 转换为 Immutable 结构。

fromJS()做的转换是深转换 (Deep Conversion)。

List

在 Immutable中,List 相当于 JavaScript 的Array(数组)。

    var list1 = Immutable.List.of('a', 'b', 'c');    var list2 = list1.push('d');    console.log(list1.size); // 输出:3
    console.log(list2.size); // 输出:4

取值与修改值使用 get() 和 set()

    var list3 = Immutable.List.of('a', 'b', 'c');    var list4 = list3.set(0, 'aa');    console.log(list3.get(0)); // 输出:a
    console.log(list4.get(0)); // 输出:aa

当 List 內部又有包其他 List 时,需要使用 setIn() 和 getIn()

    var list5 = Immutable.fromJS([1, [2, 3], 4]);    var list6 = list5.setIn([1, 1], 100);    console.log(list6.getIn([1, 0])); // 输出:2
    console.log(list6.getIn([1, 1])); // 输出:100

Map

Map 对应到的是 JavaScript 中的Objiect(对象)。Objiect并不等于是 Map,从 ES6 开始后,JavaScript 也有原生的 Map。

    var map1 = Immutable.Map({ name: 'zhangsan', sex: 'man', age: 34 });    var map2 = map1.set('age', 40);    console.log(map1.get('age')); // 输出:34
    console.log(map2.get('age')); // 输出:40

深度取值与改值也是使用 setIn() 和 getIn()

    var map3 = Immutable.fromJS({
        name: 'lisi',
        birthday: {
            year: 1979,
            month: 8,
            day: 10
        },
    });    var map4 = map3.setIn(['birthday', 'year'], 1980);    console.log(map3.getIn(['birthday', 'year'])); // 输出:1979
    console.log(map4.getIn(['birthday', 'year'])); // 输出:1980)

比较值

两个 immutable 对象使用 === 来比较,即使两个对象的值是一样的,也会返回 false。Immutable 提供了 Immutable.is 方法

    var map1 = Immutable.Map({a:1, b:1, c:1});    var map2 = Immutable.Map({a:1, b:1, c:1});    console.log(map1 === map2); // 输出:false
    console.log(Immutable.is(map1, map2)); // 输出:true

Sequence 结构

Immutable.js 的设计灵感其实有一部分來自于 Clojure, Scala, Haskell 這些函数式编程语言。因此 Immutable.js 里有个特殊的结构叫 Sequence。Map 和 List 都可以使用 toSeq() 方法来转换成 Sequence。 Sequence 有两个很重要的特性:Immutable (不可变)、Lazy (延迟)。

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

因为 Sequence 拥有延迟的特性,在取值之前他是不会把结果运算出來的。当使用 oddSquares.get(1) 才会把 9 计算出结果,但不会继续做后面 25 与 49 的运算。

因为延迟的关系,Sequence 只会做到向它要求的地,也因此,程序其实省去了很多不必要的运算,这也是为什么 Immutable.js 会优化速度的原因之一。

Range 结构本身就是 Sequence,所以不需要使用 toSeq() 来转换。

    var result = Immutable.Range(1, Infinity)
        .skip(1000)
        .map(n => -n)
        .filter(n => n % 2 === 0)
        .take(2)
        .reduce((r, n) => r * n, 1);    console.log(result); // 输出:1006008

以上代码,首先,建立一个类似 [1,2,3…Infinity] 的 Range。因为 Immutable 的关系,每一步其实都是一个新的 Range,但由于 Lazy 的特性,这些为了取值而在过程中建立的 Range 都不会被保存。skip(1000) 的作用是跳过 Range 前 1000 个值。新的 Range :[1001, 1002…Infinity]。接下来的 map() 和 filter() 会把新的 Range 过滤成 [-1002, -1004, -1006…Infinity]。take(2) 则是取 Range 內最前面兩条记录。最后再使用 reduce() 相乘得到 1006008。

参考资料

  • Immutable.js
  • Immutable.js@github
  • Immutable 详解及 React 中实践
  • Immutable.js初识

其他

  • 完整代码:https://github.com/guyoung/GyWxappCases/tree/master/Enhance