数据不可变的特性分析 rust/immutable.js

295 阅读4分钟

持而盈之,不如其已;揣而锐之,不可长保。金玉满堂,莫之能守;富贵而骄,自遗其咎。

Rust 中的不可变字符串

fn main() {
    println!("Hello, world!");
    let a = "this is string";
    println!("{}", a);
}

在 rust 中, a 开始了赋值,后面又重新赋值,rust 会报错的,报错的源影是:cannot assign twice to immutable variable

fn main() {
    println!("Hello, world!");
    let mut a = "this is string";
    println!("{}", a);

    a = "new string"
    println!("{}", a);
}

rust 很严格,如果我们在没有使用就是重新分配变量的话,会有提示

= note: `#[warn(unused_assignments)]` on by default
= help: maybe it is overwritten before being read?

总结:在 rust 中将可变数据和非可变数据分特别的清新,但是在 JS 中并没有,因为 JS 产生就是为了追求灵活。可变性的极大的增加了 JS 的灵活性,但是在 Redux 的 reduce 中,却需要不可变数据和数据的持久化。

什么是不可变数据,以及数据类型与可变数据的关系

引用数据类型数据,因为有共同的指向,数据在一处改变,对其引用的数据都会发生改变。有时候我们不希望这样,我们希望拷贝一份不可变的数据。immutable.js 能很好的帮助我们,但是在这之前,我们也有一些方法来实现。

  • Object.assign 深拷贝(第一层深拷贝)
let obj = { a: 1, b: 2 };

let assignObj = Object.assign(obj, { x: 123 });

obj === assignObj; // true

let cloneObj = Object.assign({}, obj, { x: 123 });

obj === assignObj; // false
  • JSON.stringify 转变为 JSON 字符串,然后再解析为 JSON 对象。JSON 拷贝方法不适合函数
function JSONDeepClone(obj) {
  let tmp = JSON.stringify(obj);
  let result = JSON.parse(tmp);
  return result;
}
  • Object.create 方式
  1. 先获取原型属性
  2. 获取自己的属性
  3. 遍历属性
  • 获取对象属性描述符
  • 在原型对象上扩展属性
function deepCopy(obj) {
  var copy = Object.create(Object.getPrototypeOf(obj));
  var propNames = Object.getOwnPropertyNames(obj);

  propNames.forEach(function(name) {
    var desc = Object.getOwnPropertyDescriptor(obj, name);
    Object.defineProperty(copy, name, desc);
  });

  return copy;
}
  • 最原始的是,递归遍历。
function deepClose(obj) {
  // 初始化 考虑数组和对象两种情况
  const objClone = Array.isArray(obj) ? [] : {};

  if (obj && typeof obj === "object") {
    for (let key in obj) {
      // 不考虑原型上的属性
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deepClose(obj[key]);
        } else {
          objClone[key] = deepClose(obj[key]);
        }
      }
    }
  }
}

Redux 中 reducer

我们在写 reducer 的时候,可能要特别的注意,我们不能直接返回 state, 必须返回一个 state 的副本。然后交给 store 的内容。我可能经常使用:

  • 展开运算符
  • Object.assign
  • JSON
  • ...

等方法来克隆一个新的对象完成任务,但是这个写都不够纯粹,于是 React 团队开发出了,持久数据的库 immutable.js。

理解

Immutable.js 采用了持久化数据结构和结构共享,保证每一个对象都是不可变的,任何添加、修改、删除等操作都会生成一个新的对象,且通过结构共享等方式大幅提高性能。

持久化

通俗点解释就是,对于一个持久化数据结构,每次修改后我们都会得到一个新的版本,且旧版本可以完好保留。

结构共享

我们新生成一个根节点,对于有修改的部分,把相应路径上的所有节点重新生成,对于本次操作没有修改的部分,我们可以直接把相应的旧的节点拷贝过去,这其实就是结构共享

意义

意义在于它弥补了 Javascript 没有不可变数据结构的问题。不可变数据结构是函数式编程中必备的。前端工程师被 OOP 洗脑太久了,组件根本上就是函数用法,FP 的特点更适用于前端开发。

使用 Object 获取 API

const immutable = require("immutable");

Object.keys(immutable);
  • "default",
  • "version",
  • "Collection",
  • "Iterable",
  • "Seq",
  • "Map",
  • "OrderedMap",
  • "List",
  • "Stack",
  • "Set",
  • "OrderedSet",
  • "Record",
  • "Range",
  • "Repeat",
  • "is",
  • "fromJS" 将一个 JavaScript 对象转换为 immutable 对象
  • "hash",
  • "isImmutable",
  • "isCollection",
  • "isKeyed",
  • "isIndexed",
  • "isAssociative",
  • "isOrdered",
  • "isValueObject",
  • "get", 从 immutable 对象中获取数据
  • "getIn",可以获取嵌套的 immutable 对象数据
  • "has",
  • "hasIn",
  • "merge",
  • "mergeDeep",
  • "mergeWith",
  • "mergeDeepWith",
  • "remove",
  • "removeIn",
  • "set",
  • "setIn",
  • "update",
  • "updateIn"

fromJS 将一个 JavaScript 对象转换成 Map, 获取 Map 数据结构中的数据

const immutable = require("immutable");

const immutableObj = immutable.fromJS({ a: 1, b: 2, c: 3 });

console.log(immutableObj); // Map { "a": 1, "b": 2, "c": 3 }

const aa = immutableObj.get("a"); // 1
const bb = immutableObj.get("b"); // 2
const cc = immutableObj.get("c"); // 3

// 具有嵌套结构的数据
const dd = immutable.FromJS({ x: { y: { z: "this nest value" } } });

const ddz = dd.getIn("z"); // "this nest value"

Map 对象具有 map 能够调用 map 方法, 注意,我们这里的 Map 和 ES6+ 中的 Map 是有很大的差异的。ES6+ 的 Map 的遍历方法是没有 map 方法的,但是有 forEach 方法。immutablejs 中将对象转化成 immutablejs 的 Map 之后的 Map 对象是没有。

参考