使用不可变对象的主要好处
使用不可变对象的主要好处
我目前正在使用React JS和React Native框架。在我阅读Facebook的Flux和Redux实现时,我遇到了不可变性或者Immutable-JS库的问题。问题是,为什么不可变性如此重要?改变对象有什么问题吗?这难道不会让事情变得简单吗?举个例子,让我们考虑一个简单的新闻阅读器应用,打开界面是新闻标题的列表视图。如果我设置一个数组对象的初始值,我就不能操作它了。这就是不可变性原则,对吗?(如果我错了,请纠正我。)但是,如果我有一个要更新的新闻对象呢?在通常情况下,我可以直接将对象添加到数组中。在这种情况下我该怎么做?删除存储并重新创建吗?将对象添加到数组中不是一种开销较小的操作吗?
使用不可变对象的主要好处
为什么不可变性如此重要?改变对象有什么问题吗?难道这样不会使事情变得简单吗?
实际上,相反的是正确的:可变性在长期来看会使事情变得更加复杂。是的,它可以使你最初的编码更容易,因为你可以随意更改任何东西,但是当你的程序变得更大时,这就成为了一个问题——如果一个值发生了变化,是什么改变了它呢?
当你使所有的东西都不可变时,这意味着数据不再能够被意外修改。你可以确信,如果你将一个值传递给一个函数,在该函数中它是不会被改变的。
简而言之:如果你使用不可变的值,那么你的代码将变得非常容易理解:每个人都会得到你数据的一个独立副本,所以它不会被修改并破坏你代码的其他部分。想象一下,在多线程环境中工作会变得多么容易!
注1:根据你所做的事情,不可变性可能会带来潜在的性能损失,但是像Immutable.js这样的库会尽量进行优化。
注2:如果你不确定的话,Immutable.js和ES6的const关键字有非常不同的含义。
在通常的情况下,我可以只是将对象添加到数组中。在这种情况下,我该如何做呢?删除存储并重新创建它吗?将对象添加到数组中不是一个更简单的操作吗?PS:如果这个例子不是解释不可变性的正确方式,请告诉我正确的实际例子。
是的,你的新闻例子是非常好的,你的推理是完全正确的:你不能只是修改现有的列表,所以你需要创建一个新的:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
我不反对这个答案,但它没有解决他关于“我想从一个实际例子中学习”的问题。有人可以说,一个新闻标题列表的单个引用在多个区域中使用是一件好事。“我只需要更新列表一次,所有引用新闻列表的内容都会自动更新”——我认为一个更好的答案应该提供一个常见的问题,然后展示一个使用不可变性的有价值的替代方案。
一个简单而伟大的答案!谢谢...只是一个快速的问题——假设现在我必须开始使用newItems在我的程序中,我应该清除originalItems中的值,还是GC会自动处理它?问题是,如果程序中创建了这么多变量(我指的是对象),内存使用会变得太高对吧?
我很高兴这个答案对你有帮助!关于你的新问题:不要试图去猜测系统的行为 :)在这种情况下,一种叫做“结构共享”的东西会显著降低垃圾回收的频率——如果你有一个包含10,000个项目的列表,并添加了10个项目,我相信Immutable.js会尽力尝试重新使用之前的结构。让Immutable.js去担心内存,很有可能你会发现它做得更好。
想象一下,在多线程环境中工作会变得多么容易!-> 对于其他编程语言来说这是一个优势,但在单线程的JavaScript中并不是一个优势。
请注意,JavaScript主要是异步和事件驱动的。它并不完全免疫于竞态条件。
尽管如此,我的观点仍然存在。函数式编程和不可变性是避免多线程环境中数据损坏和资源锁的强大范式,但在JavaScript中并非如此,因为它是单线程的。除非我错过了一些至关重要的智慧,否则这里的主要权衡是在是否愿意使代码变得更复杂(而且更慢),以避免竞态条件...这些竞态条件往往不像大多数人认为的那样成为问题。
我同意,如果你不担心竞态条件,那么没有不可变性你也可以做得很好。"如果它没有坏,就不要修理它。"你还说"不要相信炒作",这是另一个我同意的原则。但是,如果你超越了Redux和炒作,不可变性是一个可以极大提高代码健壮性的强大概念。它确实需要一种不同的思考方式,但如果你处理并发性(互联网),我相信它是一个非常值得学习的工具。我认为Rich Hickey在Clojure中做对了很多事情。
完全同意你的观点。我甚至会说,如果你不担心竞态条件,没有不可变性的情况下你会做得更好,除非有某种超越的架构原因可以证明额外的开销和复杂性(例如金融系统可以很好地跟踪每一笔交易)。我也同意不可变性带来了好处(主要是简化测试),但是由于复杂性的存在,存在权衡,并且最好的情况可能是将范式组合起来,以适应项目的性质,而不会让教条扰乱事情。
我发现函数式编程与不可变性可以使代码更容易跟踪和理解。但这取决于使用情况,并不总是适用。即使Clojure也承认你最终不得不改变状态,并且需要一个“机器”来处理函数式部分。幸运的是,这不是一个二元选择!我们可以并且应该在我们的工具箱中拥有各种工具。一个好的程序员有一个装备齐全的工具箱,并知道什么时候使用合适的工具。当然,总会有一个最喜欢的 🙂
使用不可变对象的主要好处
在JavaScript中,不可变性更多的是一种时尚潮流,而不是必要性。如果您正在使用React,它确实为一些在状态管理中引起困惑的设计选择提供了一个巧妙的解决方法。然而,在大多数其他情况下,它不会为复杂性引入足够的价值,更多地是为了填充简历而不是满足实际客户需求。
那么为什么在JavaScript中不可变性如此重要(或者需要)呢?这要追溯到一个名叫Dan Abramov的非常有才华的人写的一个名为Redux的JavaScript状态管理库。他还制作了一些非常酷的视频,使这个想法变得非常容易理解(并且有销售力)。时机刚刚好。Angular的新奇感正在消退,JavaScript世界准备好专注于最新的、具有适度酷炫度的事物,而这个库不仅是创新的,而且与React完美地契合,React又是由另一个硅谷巨头推动的。
可悲的是,在JavaScript的世界中,时尚在统治。现在Abramov被誉为半神,而我们这些凡人必须把自己屈服于“不变性之道”......无论它是否合理。
突然发生了什么,程序员已经修改对象......就像有对象可以修改一样长久以来(应用程序开发已有50多年)。为什么要复杂化?当你有一个对象cat,它死了,你真的需要第二个cat来跟踪这个变化吗?大多数人只会说cat.isDead = true,然后完成。
是的!当然可以!特别是在JavaScript中,它在实践中非常有用,用于渲染在其他地方(如数据库)维护的某些状态的视图。
好吧,你可以采用传统的方法,更新News对象,这样你在内存中表示的对象就会改变(用户显示的视图,或者希望如此)。或者另一种选择是,尝试一种时髦的函数式/不可变性方法,将你对News对象的更改添加到“跟踪每个历史更改”的数组中,然后遍历数组,找出正确的状态表示应该是什么(呼!)。
时尚来了,兄弟。有很多方法可以解决这个问题。
这就是问题的出现原因和解决方法,不可变对象使用的主要好处。
使用不可变对象的主要好处
最近我一直在研究同样的问题。我会尽力回答你的问题,并努力分享我已经学到的东西。
问题是,为什么不可变性如此重要?改变对象有什么问题?这难道不会让事情变得简单吗?
基本上,这是因为不可变性增加了可预测性、性能(间接地)并允许进行变异跟踪。
可预测性
变异隐藏了变化,这会导致(意外的)副作用,从而可能引发严重的错误。当你强制使用不可变性时,你可以保持应用程序的架构和心智模型简单,这样更容易理解你的应用程序。
性能
尽管向不可变对象添加值意味着需要创建一个新实例,其中需要复制现有值并添加新值到新对象中,这会消耗内存,但不可变对象可以利用结构共享来减少内存开销。
更新操作都会返回新值,但内部结构是共享的,以大幅减少内存使用(和GC抖动)。这意味着,如果你向一个有1000个元素的向量添加,它实际上不会创建一个新的长为1001的向量。很可能,内部只分配了几个小对象。
变异跟踪
除了减少内存使用外,不可变性还允许你通过使用引用和值相等性来优化应用程序。这使得查看是否有任何变化变得非常容易。例如,React组件中的状态变化。你可以使用shouldComponentUpdate来通过比较状态对象来检查状态是否相同,从而防止不必要的渲染。
如果我一开始设置了一个对象数组的值。我无法对它进行操作。这就是不可变性原则,对吗?但是,如果我有一个新的News对象需要更新怎么办?在通常情况下,我可以只需将对象添加到数组中。在这种情况下,我该如何实现?删除存储并重新创建它?将对象添加到数组是否是一个更便宜的操作?
是的,这是正确的。如果你对如何在你的应用程序中实现这一点感到困惑,我建议你看看redux是如何实现的,以熟悉核心概念,这对我帮助很大。
我喜欢使用Redux作为一个例子,因为它支持不可变性。它有一个单一的不可变状态树(称为store),所有状态更改都是通过分派操作来显式处理的,这些操作由一个接受先前状态和这些操作(一次一个)的reducer处理,并返回应用程序的下一个状态。你可以在这里阅读更多关于它的核心原则。
Redux在egghead.io上有一个出色的课程,redux的作者Dan Abramov在其中详细解释了这些原则(我稍微修改了代码以更好地适应场景)。
此外,这些视频进一步详细介绍了如何实现以下内容的不可变性:
- 数组
- 对象
感谢你的回答。这个回答非常好。然而,请扩展你的代码示例。所以addNews返回一个新的新闻数组,原来的news保持不变,但是接下来呢?你要将其赋值给...news2吗?然后下一个调用是news3吗?对于这段代码的具体处理似乎存在错误。提示:使用const关键字对于一个state变量有意义吗?状态特定地随时间改变;我甚至可以说隐含地保证。
感谢你的反馈!我的意图是阐述这个概念,并明确显示对象没有被修改,而不一定是完全实现的方法。然而,我的示例可能会有些令人困惑,我稍后会更新它。
reactjs的例子使事情更加清晰。感谢你提供如此丰富详细的答案。只有一个简短的问题,Redux已经使用了immutablejs对吧?如果我开始使用redux,就不需要引入immutablejs了。这样说对吗?
不,这是不正确的,你需要在reducer中强制实施不可变性。这意味着你可以遵循像视频中演示的策略,或者使用像immutablejs这样的库。你可以在这里找到更多信息。
这确实有帮助。基于状态的可预测性确实是一个很好的观点。感谢补充答案。
ES6的const关键字并不是关于不可变性的。Mathias Bynens写了一篇很棒的博客文章,详细介绍了这一点。
谢谢分享链接。我同意这是一个重要的区别。