原文来自:https://zhuanlan.zhihu.com/p/29983598
首先有一个非常巨大的对象…
这里有100,000条待办事项:
var todos = {
⋮
t79444dae: { title: 'Task 50001', completed: false },
t7eaf70c3: { title: 'Task 50002', completed: false },
t2fd2ffa0: { title: 'Task 50003', completed: false },
t6321775c: { title: 'Task 50004', completed: false },
t2148bf88: { title: 'Task 50005', completed: false },
t9e37b9b6: { title: 'Task 50006', completed: false },
tb5b1b6ae: { title: 'Task 50007', completed: false },
tfe88b26d: { title: 'Task 50008', completed: false },
⋮
(100,000 items)
}
我要把第50,005条任务的completed改为ture。
用普通的JavaScript对象
function toggleTodo (todos, id) {
return Object.assign({ }, todos, {
[id]: Object.assign({ }, todos[id], {
completed: !todos[id].completed
})
})
}
var nextState = toggleTodo(todos, 't2148bf88')
这项操作运行了134ms。
为什么用了这么长时间呢?因为当使用Object.assign,JavaScript会从旧对象(浅)复制每个属性到新的对象。
我们有100,000条待办事项,所以意味着有100,000个属性需要被(浅)复制。
这就是为什么花了这么长时间的原因。
在JavaScript中,对象默认是可变的。
当你复制一个对象时,JavaScript不得不复制每一个属性来保证这两个对象相互独立。
100,000个属性被(浅)复制到新的对象。
使用Immutable.js
var todos = Immutable.fromJS({
⋮
t79444dae: { title: 'Task 50001', completed: false },
t7eaf70c3: { title: 'Task 50002', completed: false },
t2fd2ffa0: { title: 'Task 50003', completed: false },
t6321775c: { title: 'Task 50004', completed: false },
t2148bf88: { title: 'Task 50005', completed: false },
t9e37b9b6: { title: 'Task 50006', completed: false },
tb5b1b6ae: { title: 'Task 50007', completed: false },
tfe88b26d: { title: 'Task 50008', completed: false },
⋮
(100,000 items)
})
// 使用[updeep](https://github.com/substantial/updeep)
function toggleTodo (todos, id) {
return u({
[id]: {
completed: (completed) => !completed
}
}, todos)
}
var nextState = toggleTodo(todos, 't2148bf88')
这项操作运行了1.2ms。速度提升了100倍。
为什么会这么快呢?
可持久化的数据结构
可持久化的数据结构强制约束所有的操作将返回新版本数据结构并且保持原有的数据结构不变,而不是直接修改原来的数据结构。
这意味着所有的可持久化数据结构是不可变的。
鉴于这个约束,第三方库在应用可持久化数据结构后可以更好的优化性能。
性能提升
为了直观的观察,让我们尝试一个小小例子。
键值对映射数据:
我们可以用一个普通的JavaScript对象来保存:
const data = {
to: 7,
tea: 3,
ted: 4,
ten: 12,
A: 15,
i: 11,
in: 5,
inn: 9
}
转换成一棵数据树,像这样:
图片来源: 维基百科
通常来讲,你可以从根节点沿着上图的路径来获取你想要的值。
如果你想获取http://data.in,从根节点开始,沿着i,n,就可以到达值为5的节点。
如何去修改呢?
比如我们想把键为tea的值3改成14。
我要建造一个新的树,并且尽可能重用已经存在的节点
绿色表示新建的节点。
灰色的节点失去的索引,将会被回收
正如你所看到的,我们只是重新创建了4个新的节点来更新这个树。其他节点都被重用了。
这叫 结构共享
Immutable.js就是通过这种方式实现Immutable.Map。创建一个树,每个节点最多有32个分支。
这张图展示了一个真实的Immutable.Map
当我们更新一个节点,只有几个节点需要被重新创建。
Immutable.js通过创建多种类型的节点来保持树结构的紧凑和性能:
万物皆有两面性...
请不要认为这篇文章的意思是“你应该经常使用Immutable.js。”,准确的讲,我只是告诉你用它的所有好处,以及为什么要使用它。
当我在写代码的时候,我首先会用普通的JavaScript对象和数组,当我使用Immutable.js时,我需要非常确定,比如一个对象包含10,000个属性。我几乎从不使用Immutable.js,因为大多数时候的对象都很小。