使用 immutable.js 优于 Object.assign 或展开运算符的优势

Advantages of using immutable.js over Object.assign or spread operators

到目前为止,我看到的大多数 "starter boilerplates" 和一些关于 react / redux 的帖子都鼓励使用 immutable.js 来解决可变性问题。我个人依靠 Object.assign 或传播运算符来处理这个问题,因此并没有真正看到 immutable.js 的优势,因为它增加了额外的学习并从用于可变性的香草 js 技术转移了一点。我试图找到转换的正当理由,但无法找到,因此我想在这里问一下为什么它如此受欢迎。

immutable.js 为您提供了很多实用程序,这些实用程序总是 return 一个新对象。

示例:

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

对于 react / redux 我个人认为 Object.assign 足够强大,但在某些情况下使用该库可以为您节省几行代码。

immutable.js 的优点是它强制 一个不可变的 redux 存储(你可能会忘记扩展,或者草率地保护你的存储免受修改)并且 Object.assign 或扩展运算符相比大大简化了 减速器(顺便说一句,两者都很浅!)。

据说我在一个大的Angular 1.x项目中使用了redux+immutable,但有缺点:性能问题,不清楚什么是不可变的,什么不是,Angular 不能在 ng-repeat 中使用 immutable.js 结构,等等。虽然对 React 不确定,但很想听听意见。

我认为 ImmutableJs 的主要优势在于它的数据结构和速度。当然,它也强制不变性,但无论如何你都应该这样做,所以这只是一个额外的好处。

例如,假设您的 reducer 中有一个非常大的对象,您想要更改该对象的很小一部分。由于不变性,您不能直接更改对象,但必须创建对象的副本。您可以通过复制所有内容来做到这一点(在 ES6 中使用扩展运算符)。

所以问题是什么?复制非常大的对象非常慢。 ImmutableJS 中的数据结构做一些叫做 结构共享 的事情,你实际上只改变你想要的数据。您未更改的其他数据在对象之间共享,因此不会被复制。

这样做的结果是高效的数据结构,写入速度很快。

ImmutableJS 还提供了对深层对象的简单比较。例如

const a = Immutable.Map({ a: Immutable.Map({ a: 'a', b: 'b'}), b: 'b'});
const b = Immutable.Map({ a: Immutable.Map({ a: 'a', b: 'b'}), b: 'b'});

console.log(a.equals(b)) // true 

如果没有这个,你需要某种深度比较函数,这也会花费很多时间,而这里的根节点包含整个数据结构的哈希(不要引用我的话,这个我记得是这样,但比较总是即时的),所以比较总是 O(1) 即即时的,无论对象大小如何。

这在 React shouldComponentUpdate 方法中特别有用,您可以使用此 equals 函数比较道具,该函数会立即运行。

当然,也有不好的地方,如果你把immutablejs结构和常规对象混在一起,就很难分清是什么了。此外,您的代码库中充斥着 immutableJS 语法,这与常规 Javascript.

不同

另一个缺点是如果你不打算使用深度嵌套的对象,它会比普通的旧 js 慢一点,因为数据结构确实有一些开销。

只是我的 2 美分。

这就是效率。

持久数据结构

持久数据结构在通过始终产生新数据结构进行变异时保留其自身的先前版本。为了避免昂贵的克隆,只存储与先前数据结构的差异,而在它们之间共享交集。这种策略称为结构共享。因此,持久数据结构比使用 Object.assign 或扩展运算符克隆更有效。

Javascript

中持久数据结构的缺点

不幸的是Javascript 本身不支持持久数据结构。这就是 immutable.js 存在的原因,它的对象与普通的旧 Javascript Objects 有很大不同。这会导致更冗长的代码和大量的持久数据结构到原生 Javascript 数据结构的转换。

关键问题

immutable.js 结构共享(效率)的好处何时会超过其缺点(冗长、转换)?

我猜这个库只有在具有大量和大量对象和集合的大型项目中才有回报,当克隆整个数据结构和垃圾收集变得更加昂贵时。

我已经为多个不可变库创建了性能基准,脚本和结果位于 immutable-assign (GitHub project), which shows that immutable.js 中针对写入操作进行了优化,比 Object.assign() 快,但是速度较慢用于读取操作。以下是基准测试结果的摘要:

-- Mutable
Total elapsed = 50 ms (read) + 53 ms (write) = 103 ms.

-- Immutable (Object.assign)
Total elapsed = 50 ms (read) + 2149 ms (write) = 2199 ms.

-- Immutable (immutable.js)
Total elapsed = 638 ms (read) + 1052 ms (write) = 1690 ms.

-- Immutable (seamless-immutable)
Total elapsed = 31 ms (read) + 91302 ms (write) = 91333 ms.

-- Immutable (immutable-assign (created by me))
Total elapsed = 50 ms (read) + 2173 ms (write) = 2223 ms.

因此,是否使用immutable.js将取决于您的应用程序类型及其读写比率。如果您有很多写操作,那么 immutable.js 将是一个不错的选择。

Premature optimization is the root of all evil

理想情况下,您应该在引入任何性能优化之前分析您的应用程序,但是,不变性是必须尽早决定的设计决策之一。当您开始使用 immutable.js 时,您需要在整个应用程序中使用它以获得性能优势,因为使用 fromJS() 和 toJS() 与普通 JS 对象的互操作非常昂贵。

这不仅仅是通过持久数据结构提高性能。不变性本身就是非常理想的品质,因为它完全消除了由意外突变引起的任何错误。另外,在 Javascript 中使用方便的 API 的理智数据结构是很好的,一些实际可用的东西,并且 immutable.js 远远领先于普通对象和数组,即使最近添加了 ES6 和 ES7 .就像 jQuery - 它之所以如此受欢迎,是因为它的 API 与香草 DOM API(这只是一场噩梦)相比确实很棒且易于使用。当然,您可以尝试使用普通对象保持不可变,但是您不必忘记 Object.assign 和数组在每个该死的时间传播,并且使用 immutable.js 您根本不会出错,这里的任何突变是明确的