本文作者:冬柠
前言
今天在用 immutable 改写以前的旧代码。数据的嵌套结构有可能很深的情况, 内部对于数据的增删改查和各种循环递归操作有很多。用了 immutable.js (其实也配合使用了TypeScript,效果更佳) 来改造了下。一个总结体验那就是爽~
immutable初相识
immutable 的英文意思是“不可变的”。从名字就已经体现出 immutable.js 的精髓了。在immutable的世界观里, 一切都是不可变的数据。对 Immutable 数据的修改会返回一个新的 Immutable 对象。
而传统的Js里十分让人头疼的一点就是对象的数据传递是引用传递, 更新了新的对象也会影响原有的对象。从而使用各种 deepExtend, deepCopy方法来解决这个问题。而使用immutable.js就没有这个烦恼。
举个栗子:
let obj1 = Immutable.Map({ a: 1, b: 2, c: 3 })
let obj2 = obj1
obj2 === obj1
// return true(此时obj2实例和obj1实例指向了同一个地址)
obj2 = obj2.set("b",4)
// 此时obj2已经被更新,指向了另一个地址
obj2 === obj1
// return false
// 改变obj2并不影响obj1
常用的概念和操作
常用的对象
- Map: 键值对结构,通常对应js中的object结构转换过来的类型。但是和js的object的很多特性有明显的区分。
Object只接受字符串作为键名,然而Map接受任何类型的数据作为键名。
let obj = { 1: "one" }
Object.keys(obj) // [ "1" ]
console.log(obj["1"], obj[1]) // "one","one"
let map = Map(obj)
console.log(map.get("1"), map.get(1)) // "one", undefined
Map支持键值对的遍历(map,forEach,filter
)等操作。大大方便了js中Object.keys()
来遍历对象的写法
2. OrderedMap: 一种有序的Map类型。弥补了Object对象无序的缺憾。
3. List: 有序列表, 对应js中的Array
4. Set: 不可重复的列表
常用的操作
1.和js数据类型相互转化
Immutable.Map({})
对象换成MapImmutable.List([])
数组换成ListImmutable.formJS()
- 识别传入的数据并自动转换成相应
Map | List
类型 - 对于Immutable对象, 有
toJS()
,toJSON()
,toArray()
,toObject()
方法转换成相应的原生Js结构。
2.判断两个对象的值是否相等
let obj1 = Immutable.Map({ a: 1, b: 2, c: 3 })
let obj2 = Immutable.Map({ a: 1, b: 2, c: 3 })
Immutable.is(obj1, obj2); // or obj1.equals(obj2)
// return ture
3.深度取值
let map1 = Immutable.Map({ a: {d: { target : 1}}, b: 2, c: 3 })
const target = Immutable.getIn(map1, ["a","d","target"])
// target: 1
4.深度赋值
有一个cursor(游标)的概念, 可以进行深度赋值的操作。但会在下一个版本即将被废弃。建议使用setIn
来代替。
let map1 = Immutable.Map({ a: {d: { target : 1}}, b: 2, c: 3 })
map1 = Immutable.setIn(map1, ["a","d","target"], 4)
// map1: { a: {d: { target : 4}}, b: 2, c: 3 }
5.浅合并(类似于Object.assign, 只合并了第一层)
const map1 = Immutable.Map({ a: {d: { target : 1}}});
const map2 = Immutable.Map({ a: {c: 1}});
const map3 = Immutable.merge(map1,map2)
console.log(map3.toJS())
// { a: {c: 1}}
6.深度合并
const map1 = Immutable.Map({ a: {d: { target : 1}}});
const map2 = Immutable.Map({ a: {c: 1}});
const map3 = Immutable.mergeDeep(map1,map2)
console.log(map3.toJS())
// { a: {c: 1, d: { target : 1}}}}
优势
1.节省内存
- 使用了Trie数据结构来进行储存。使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
比如对象
const data = {
to: 7,
tea: 3,
ted: 4,
ten: 12,
A: 15,
i: 11,
in: 5,
inn: 9
}
根据trie结构,存储的结构类似于
如果更改了了tea字段 3 -\> 14,那么只需改变四个节点,来更新形成新的树, 这就是结构共享。
2.提升计算性能
使用了 Trie数据结构。避免深度比较。如果两个对象的值相等。那么他们的hashCode也相等。从而比较hashCode即可比较两个对象的值是否相等,避免了深度比较。
3.拥抱函数式编程
4.使用便利
- 因为数据是不可变的。同时提供的API面向开发者也十分友好。
使用immutable的注意事项
1.变量命名
非常值得注意的一个点。因为可能引用了其他的库或者文件,使得代码里存在immutable数据和非immutable数据。所以immutable变量需要加上 ? 前缀来区分
2.API上的习惯
赋值操作之后要修改原数据记得要赋值。不然无法更新数据。
?data = ?data.set("hello","immutable")
3.无法使用function传值来修改数据
let ?data = Map({hello: "immutable"});
function changeData(?data){
?data = ?data.set("hello","world")
}
changeData(?data)
console.log(?data.toJS())
// {hello: "immutable"}
如上, ?data并不会改变。所以在写改变数据的方法的时候需要返回新值。然后覆盖旧值。
let ?data = Map({hello: "immutable"});
function changeData(?data){
return ?data.set("hello","world")
}
?data = changeData(?data)
console.log(?data.toJS())
// {hello: "world"}
4.为了性能和节省内存, Immutable.js 会努力避免创建新的对象。如果没有数据变化发生的话。
const { Map } = require('immutable')
const originalMap = Map({ a: 1, b: 2, c: 3 })
const updatedMap = originalMap.set('b', 2)
updatedMap === originalMap // return ture
如上代码虽然进行了一顿操作。然而数据并没有改变。所以updatedMap和originalMap还是指向了同一个对象。
const updatedMap = originalMap.set('b', 4)
updatedMap === originalMap
// return false
此时返回false
5.Immutable的使用场景
虽然immutable有种种好处, 但并不是在所有场景都建议使用Immutable, 对于数据结构复杂,操作很多的使用immutable比较合适。但对于简单的应用场景, 还是使用原生的js吧。