对不可变性的反向观点
简而言之:在JavaScript中,不可变性更多地是一种时尚潮流,而非必需品。如果你正在使用React,它确实为一些令人困惑的设计选择提供了一个巧妙的解决方案来管理状态。然而,在大多数其他情况下,它并不能带来足够的价值,而只会引入复杂性,更多地用于填充简历,而不是满足实际客户需求。
长篇回答请见下文。
为什么在JavaScript中不可变性如此重要(或需要)?
好吧,我很高兴你问到了这个问题!
不久前,一个非常有才华的家伙叫做Dan Abramov写了一个名为Redux的JavaScript状态管理库,它使用纯函数和不可变性。他还制作了一些非常酷的视频,使这个想法变得非常容易理解(并且销售)。
时机恰到好处。Angular的新奇感正在消退,而JavaScript世界正准备专注于最新的、具有适度酷炫程度的东西,而这个库不仅创新,而且与另一个硅谷巨头推销的React完美契合。
尽管令人沮丧,但在JavaScript世界中,时尚统治着。现在,Abramov被称为半神,并且我们这些凡人必须屈服于不可变性之道……无论它是否有意义。
改变对象有何不妥之处?
没有!
实际上,程序员一直以来都在改变对象,嗯... 自从存在对象以来已经有了50多年的应用开发。
而为什么要把事情弄复杂呢?当你有一个对象cat
并且它死去时,你真的需要第二个cat
来跟踪这个变化吗?大多数人只会说cat.isDead = true
就行了。
(改变对象)难道不让事情变得简单吗?
是的!..当然会!
在JavaScript中,特别是在实践中,最常用于呈现某些在其他地方维护的状态的视图(比如数据库)。
如果我有一个需要更新的新闻对象...在这种情况下我该怎么做呢?删除存储并重新创建它吗?将对象添加到数组中不是一种更廉价的操作吗?
嗯,你可以采用传统的方法,更新新闻对象,这样你内存中表示的对象就会改变(用户看到的视图也会相应改变,或者希望如此)...
或者...
你可以尝试时髦的函数式编程/不可变性方法,将你的更改添加到新闻对象的一个跟踪每个历史更改的数组中,然后通过遍历数组来确定正确的状态表示应该是什么(哇!)。
我正在努力学习什么是正确的。请给我指点一下吧 :)
时尚来了又走,伙计。有很多种方法可以解决问题。
很抱歉你必须忍受不断变化的编程范式的困惑。但是,欢迎加入这个俱乐部!
现在有几个关于不可变性的重要要点需要记住,而且你会以只有天真才能拥有的狂热强度被抛出来。
1)不可变性对于避免多线程环境中的竞态条件非常棒。
多线程环境(如C++、Java和C#)在多个线程想要更改对象时会锁定它们。这对性能来说是不好的,但比数据损坏的替代方案要好。然而,并不像使所有东西都不可变那样好(上帝保佑Haskell!)。
但是哎呀!在JavaScript中,你总是在单个线程上操作。即使是Web Worker(每个运行在一个单独的上下文中)。所以,由于你不能在执行上下文中出现与线程相关的竞态条件(所有那些可爱的全局变量和闭包),不可变性的主要优点就不存在了。
(话虽如此,在 Web Workers 中使用纯函数也有优势,就是你不需要对主线程上的对象进行调整的期望。)
2) 不可变性(Immutability)可以(在某种程度上)避免应用程序状态的竞态条件。
这才是关键,大多数(React)开发者会告诉你,不可变性和函数式编程可以以某种方式实现让应用程序状态变得可预测的魔法。
当然,这并不意味着你可以避免数据库中的 竞态条件,要做到这一点,你需要协调所有浏览器中的所有用户,为此,你需要像WebSockets这样的后端推送技术(下面将详细介绍),它将向运行该应用程序的每个人广播更改。
这也不意味着 JavaScript 存在某种固有问题,需要通过不可变性来使应用程序状态变得可预测,任何在 React 之前编写过前端应用程序的开发者都会告诉你这一点。
这个相当令人困惑的说法简单地意味着,如果你使用React,你的应用程序容易出现竞态条件,但是不可变性可以帮助你摆脱这种痛苦。为什么?因为React很特殊...它首先被设计为一个高度优化的渲染库,而状态管理则被削弱到这个目标,因此组件状态通过异步事件链(也称为"单向数据绑定")进行管理,以优化渲染,但你无法控制它,并且依赖于你记住不直接改变状态...
在这种情况下,很容易看出不可变性与JavaScript关系不大,而与React有很大关系:如果您的全新应用程序中存在一堆相互依赖的更改,并且没有简单的方法来确定您当前的状态,
您将感到困惑,因此
使用不可变性来跟踪每个历史更改是完全合理的。
3) 竞态条件绝对是不好的。
嗯,如果您使用React可能会出现这种情况。但是,如果选择其他框架,它们就很少见。
此外,您通常还有更大的问题要处理...像依赖地狱这样的问题。像臃肿的代码库。像CSS加载不成功。像缓慢的构建过程或者被困在导致迭代几乎不可能的单块式后端。像经验不足的开发人员不明白发生了什么,并把事情搞得一团糟。
你知道的。现实。但嘿,谁在乎呢?
4) 不可变性利用
引用类型来减少跟踪每个状态变化的性能影响。
因为说真的,如果你每次状态改变都要复制东西,那你最好确保自己聪明一些。
5) 不可变性允许你撤销操作。
因为嗯...这是你的项目经理最想要的功能,对吧?
6) 不可变状态与WebSockets结合具有很多潜力。
最后但并非最不重要的是,状态增量的累积与WebSockets相结合,可以轻松消费
作为不可变事件流的状态,这是一个非常有说服力的案例...
一旦这个概念(状态是事件的流动,而不是粗糙的记录集合代表最新视图)被理解,不可变的世界就变成了一个令人惊叹的居住之地。一个超越时间本身的奇迹和可能性之地,一个event-sourced的乐土。当正确实施时,这可以使实时应用程序更容易实现,只需将事件的流动广播给所有感兴趣的人,他们可以建立自己的表示并将自己的变化写回到共同的流中。
但在某个时候,你会醒悟到所有那些奇妙和魔力并非免费获取。与你热情的同事不同,你的利益相关者(是的,就是支付你工资的人)对哲学或时尚不太关心,他们更在意为产品付费建设所花费的金钱。底线是,编写不可变代码更加困难,而破坏它则更容易,此外,如果没有后端支持,拥有不可变前端也毫无意义。当(如果!)你最终说服你的利益相关者通过推送技术如WebSockets发布和消费事件时,你会发现在生产环境中扩展的困难之处。
现在给你一些建议,如果你选择接受的话。
选择使用FP/Immutability来编写JavaScript代码也意味着让你的应用程序代码库变得更大、更复杂和更难管理。我强烈建议将这种方法限制在Redux reducers中,除非你知道自己在做什么... 如果你无论如何都要使用不可变性,那么请将
不可变状态应用到整个应用程序堆栈,而不仅仅是客户端。毕竟,如果前端是不可变的,然后将其连接到一个数据库,其中所有记录都有一个可变版本,那就回到了你试图摆脱的同样问题!
现在,如果你有幸能在工作中作出选择,那么请尽量运用你的智慧(或者不运用),为支付你工资的人做正确的事情。你可以基于自己的经验、直觉,或者周围发生的事情来决策(不可否认的是,如果每个人都在使用React/Redux,那么有一个合理的论点是更容易找到资源继续你的工作)。另外,你也可以尝试以简历为驱动的开发或以炒作为驱动的开发方法。它们可能更适合你。
简而言之,关于不变性的好处就是,至少会让你在同行中时尚一段时间,直到下一个狂热出现,到那时你会庆幸继续前进。
现在,在这次自我疗法之后,我想指出我已将其添加为我的博客文章 =>
JavaScript中的不变性:一个反对者的观点。如果你也有强烈的感受想要发表,请随意在那里回复 ;)。