为什么我应该在object.freeze上使用immutablejs

Why should I use immutablejs over object.freeze?

本文关键字:immutablejs freeze 我应该 object 为什么      更新时间:2023-09-26

我在网上研究过immutablejs相对于Object.freeze()的好处,但没有发现任何令人满意的东西!

我的问题是,当我可以冻结一个普通的旧javascript对象时,为什么我应该使用这个库并使用非本机数据结构?

我认为您不了解immutablejs提供了什么。它不是一个只使对象变为不可变的库,而是一个处理不可变值的库。

在不简单重复他们的文档和任务说明的情况下,我将说明它提供的两件事:

  1. 类型。他们实现了(不可变的)无限范围、堆栈、有序集、列表。。。

  2. 它们的所有类型都实现为持久数据结构。

我撒谎了,这里引用了他们的使命声明:

不可变数据一旦创建就无法更改,从而使应用程序开发更加简单,无需防御性复制,并使用简单的逻辑实现高级存储和更改检测技术。持久性数据提供了一个变异的API,它不就地更新数据,而是总是产生新的更新数据。

我敦促你阅读他们链接到的文章和视频,以及更多关于持久数据结构的内容(因为它们是immutablejs的内容),但我会用一句话左右总结:

假设你正在写一个游戏,你有一个玩家坐在2d平面上。例如,这里是Bob:

var player = {
  name: 'Bob',
  favouriteColor: 'moldy mustard',
  x: 4,
  y: 10
};

既然你喝了FP koolad,你就想把玩家冻住(brrr!希望Bob能得到一件毛衣):

var player = Object.freeze({
    name: 'Bob',
    ...
});

现在进入游戏循环。在每一次勾选中,玩家的位置都会发生变化。我们不能只更新玩家对象,因为它被冻结了,所以我们复制它:

function movePlayer(player, newX, newY) {
    return Object.freeze(Object.assign({}, player, { x: newX, y: newY }));
}

这很好,但请注意,我们正在进行大量无用的复制:在每一次勾选中,我们都会创建一个新对象,迭代其中一个对象,然后在其上分配一些新值。在每一个刻度上,在你的每一个物体上。真是一口。

Immutable为您包装:

var player = Immutable.Map({
    name: 'Bob',
    ...
});
function movePlayer(player, newX, newY) {
    return player.set('x', newX).set('y', newY);
}

通过ノ*✧゚魔术✧゚*ヽ对于持久数据结构,他们承诺尽可能减少操作量。

心态也有差异。当使用"一个普通的旧[冻结]javascript对象"时,everything的默认操作是假设可变性,并且您必须付出额外的努力来实现有意义的不变性(也就是说,不变性承认状态存在)。这也是freeze存在的部分原因:当你试图做其他事情时,事情就会恐慌。对于Immutablejs,不变性当然是默认的假设,它上面有一个很好的API

这并不是说所有的东西都是粉红色和玫瑰色的,上面有樱桃。当然,每件事都有它的缺点,你不应该因为可以就到处塞Immutable。有时候,仅仅freeze处理一个对象就足够了。见鬼,大多数时候这是远远不够的。这是一个有用的图书馆,有它的利基市场,只是不要被炒作冲昏头脑。

根据我的基准测试,immutable.js是为写操作优化的,比Object.assign()快,但读操作慢。因此,描述取决于应用程序的类型及其读/写比率。以下是基准测试结果摘要:

-- Mutable
Total elapsed = 103 ms = 50 ms (read) + 53 ms (write).
-- Immutable (Object.assign)
Total elapsed = 2199 ms = 50 ms (read) + 2149 ms (write).
-- Immutable (immutable.js)
Total elapsed = 1690 ms = 638 ms (read) + 1052 ms (write).
-- Immutable (seamless-immutable)
Total elapsed = 91333 ms = 31 ms (read) + 91302 ms (write).
-- Immutable (immutable-assign (created by me))
Total elapsed = 2223 ms = 50 ms (read) + 2173 ms (write).

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

PS:刚刚发现深度冻结数组(1000个元素)的更新速度非常慢,大约慢了50倍,因此您只能在开发模式下使用深度冻结。基准结果:

-- Immutable (Object.assign) + deep freeze
Total elapsed = 45903 ms = 96 ms (read) + 45807 ms (write).

它们都不会使对象变得非常不可变。

但是,使用Object.freeze,您将不得不自己创建对象/数组的新实例,并且它们不会具有结构共享。因此,每一次需要深入复制所有内容的更改,旧的集合都将被垃圾收集。

另一方面,immutablejs将管理集合,当某些内容发生更改时,新实例将使用旧实例中未更改的部分,从而减少复制和垃圾收集。

Object.freeze()和immutable.js.之间有几个主要区别

让我们先解决性能成本问题。Object.freeze()很浅。它将使对象不可变,但所述对象内部的嵌套属性和方法仍然可以发生变化。Object.freeze()文档解决了这一问题,甚至提供了一个"deepFreeze"函数,该函数在性能方面的成本甚至更高。另一方面,Immutable.js将以更低的成本使对象作为一个整体(嵌套的属性、方法等)不可变。

此外,如果您需要克隆不可变变量,Object.freeze()将强制您创建一个全新的变量,而immutable.js可以重用现有的不可变变量来更有效地创建克隆。这篇文章中有一句有趣的话:

"像.set()这样的不可变方法可能比克隆更有效因为他们让新对象引用旧对象中的数据:only更改后的属性不同。这样可以节省内存性能,而不是不断深入克隆一切。"

简而言之,Immutable.js在旧的和新的不可变变量之间建立了逻辑连接,从而提高了克隆的性能和冻结变量在内存中占用的空间。不幸的是,Object.freeze()没有——每次从冻结的对象中克隆一个新变量时,基本上都会重新写入所有数据,而且这两个不可变变量之间没有逻辑连接,即使(出于某种奇怪的原因)它们持有相同的数据。

因此,就性能而言,尤其是如果您经常在程序中使用不可变变量,immutable.js是一个不错的选择。然而,性能并不是一切,使用Immutable.js也有一些重要的注意事项。Immutable.js使用自己的数据结构,这使得调试,甚至只是将数据记录到控制台,成为一种巨大的痛苦。它还可能导致JavaScript基本功能的丢失(例如,您不能使用ES6去结构化)Immutable.js文档臭名昭著地无法理解(因为它最初是为仅在Facebook内部使用而编写的),即使出现简单问题,也需要大量的网络搜索。

我希望这涵盖了两种方法中最重要的方面,并帮助您决定哪种方法最适合您。

Object.freeze本身不进行任何深度冷冻,我相信immutable.js可以。

与任何库相同——为什么要使用下划线、jquery等

人们喜欢重新使用其他人构建的轮子:-)

脑海中浮现的最大原因-除了有一个帮助进行不可变更新的功能api之外,是Immutable.js使用的结构共享。如果你有一个应用程序需要强制的不变性(即,你使用的是Redux),那么如果你只使用Object.freeze,那么你将为每个"突变"制作一个副本。随着时间的推移,这并不是真正有效的,因为这将导致GC thrasing。使用Immutable.js,您可以实现结构共享(而不是必须实现对象池/自己的结构共享模型),因为从不可变返回的数据结构是Tries。这意味着所有的突变仍然在数据结构中被引用,因此GC攻击被保持在最低限度。关于这方面的更多信息,请访问Immutable.js的文档网站(以及创作者Lee Byron的一段更深入的精彩视频):

https://facebook.github.io/immutable-js/