我目前正在开发React JS和React Native框架。在半路上,当我读到Facebook的Flux和Redux实现时,我遇到了Immutability或Immutable-JS库。

问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?

举个例子,让我们考虑一个简单的新闻阅读器应用程序,它的打开屏幕是一个新闻标题的列表视图。

如果我设置一个带有值的对象数组,一开始我不能操作它。这就是不可变原理,对吧?(如果我说错了请指正。) 但是,如果我有一个新的News对象需要更新怎么办?通常情况下,我可以将对象添加到数组中。 在这种情况下我该如何实现呢?删除存储并重新创建它? 向数组中添加对象难道不是一种成本较低的操作吗?


当前回答

不同的看法……

我的另一个答案是从非常实际的角度来解决这个问题的,我仍然喜欢它。我决定把这个作为另一个答案,而不是对那个答案的补充,因为它是一个无聊的哲学咆哮,希望它也回答了这个问题,但并不真正符合我现有的答案。

博士TL;

即使在小型项目中,不变性也很有用,但不要认为它的存在就是为你准备的。

答案要长得多

注意:为了这个回答的目的,我使用“纪律”这个词来表示为了某些好处而自我否定。

这在形式上类似于另一个问题:“我应该使用Typescript吗?”为什么类型在JavaScript中如此重要?”它也有类似的答案。考虑以下场景:

你是5000行JavaScript/CSS/HTML代码库的唯一作者和维护者。你半技术性的老板读了一些关于typescript的最新热点,并建议我们可能想要改用它,但把决定留给你。所以你读到它,玩它,等等。

现在你要做一个选择,你是转到Typescript?

Typescript has some compelling advantages: intellisense, catching errors early, specifying your APIs upfront, ease of fixing things when refactoring breaks them, fewer tests. Typescript also has some costs: certain very natural and correct JavaScript idioms can be tricky to model in it's not-especially-powerful type system, annotations grow the LoC, time and effort of rewriting existing codebase, extra step in the build pipeline, etc. More fundamentally, it carves out a subset of possible correct JavaScript programs in exchange for the promise that your code is more likely to be correct. It's arbitrarily restrictive. That's the whole point: you impose some discipline that limits you (hopefully from shooting yourself in the foot).

回到上一段所提到的问题:值得这样做吗?

在上述场景中,我认为,如果您非常熟悉中小型JS代码库,那么选择使用Typescript更具有美感而非实用性。这很好,美学并没有错,只是它们不一定引人注目。

场景2:

你换了工作,现在是Foo公司的一名业务线程序员。你和一个10人团队一起工作,开发90000个LoC(还在不断增加)JavaScript/HTML/CSS代码库,以及一个相当复杂的构建管道,包括babel、webpack、一套polyfills、react与各种插件、一个状态管理系统、大约20个第三方库、大约10个内部库、编辑器插件(如带有内部风格指南规则的linter)等等。

Back when you were 5k LoC guy/girl, it just didn't matter that much. Even documentation wasn't that big a deal, even coming back to a particular portion of the code after 6 months you could figure it out easily enough. But now discipline isn't just nice but necessary. That discipline may not involve Typescript, but will likely involve some form of static analysis as well as all the other forms of coding discipline (documentation, style guide, build scripts, regression testing, CI). Discipline is no longer a luxury, it is a necessity.

所有这些都适用于1978年的GOTO:你的C语言的小21点游戏可以使用GOTO和意大利面逻辑,选择你自己的冒险方式并不是什么大不了的事情,但随着程序变得越来越大,野心越来越大,那么,无序地使用GOTO就不能持续下去了。所有这些都适用于今天的不变性。

就像静态类型一样,如果你不是在一个由工程师团队维护/扩展的大型代码库上工作,那么使用不可变性的选择更多的是美观而不是实用:它的好处仍然存在,但可能还没有超过成本。

但就像所有有用的学科一样,到了一定程度,它就不再是可有可无的了。如果我想保持健康的体重,那么关于冰淇淋的纪律可能是可有可无的。但如果我想成为一名有竞争力的运动员,我对是否吃冰淇淋的选择就包含在我对目标的选择之中。如果你想用软件改变世界,不可变性可能是你避免它在自身重压下崩溃所需要的一部分。

其他回答

Javascript中不可变的另一个好处是减少了时间耦合,这对设计有很大的好处。考虑一个具有两个方法的对象的接口:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

可能需要调用baz()来使对象处于有效状态,从而调用bar()才能正确工作。但你是怎么知道的?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

要弄清楚这一点,您需要仔细检查类的内部结构,因为从检查公共接口中无法立即看出这一点。这个问题可能会在具有大量可变状态和类的大型代码库中爆发。

如果Foo是不可变的,那么这就不再是一个问题。假设我们可以以任何顺序调用baz或bar是安全的,因为类的内部状态不能改变。

问题是,为什么不变性如此重要?改变对象有什么错?这不是让事情变得简单了吗?

事实上,情况恰恰相反:至少从长远来看,可变性会让事情变得更复杂。是的,它使你的初始编码更容易,因为你可以在任何你想要的地方修改东西,但当你的程序变大时,它就变成了一个问题——如果一个值改变了,是什么改变了它?

当你让所有东西都是不可变的,这意味着数据不能再被意外改变了。你肯定知道,如果你把一个值传递给一个函数,它就不能在那个函数中被改变。

简单地说:如果你使用不可变的值,它会让你的代码变得非常容易:每个人都有一个唯一的数据副本,所以它不会破坏它,破坏你代码的其他部分。想象一下,这使得在多线程环境中工作变得多么容易!

注1:不变性有潜在的性能成本,这取决于你在做什么,但像Immutable.js这样的东西会尽其所能优化。

注2:在你不确定的情况下,Immutable.js和ES6的const意味着非常不同的东西。

通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现呢?删除商店并重新创建它?向数组中添加对象难道不是一种成本较低的操作吗?PS:如果这个例子不是解释不变性的正确方式,请让我知道什么是正确的实际例子。

是的,你的新闻例子非常好,你的推理也非常正确:你不能只是修改现有的列表,所以你需要创建一个新的列表:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

从前,线程之间的数据同步出现了一个问题。这个问题非常麻烦,有10多个解决方案。有些人试图从根本上解决这个问题。它是函数式编程诞生的地方。这就像马克思主义。我不明白Dan Abramov是怎么把这个想法卖给JS的,因为它是单线程的。他是个天才。

我可以举个小例子。在gcc中有一个属性__attribute__((pure))。编译器试图解决你的函数是否是纯的,如果你不特别声明它。你的函数可以是纯的,即使你的状态是可变的。不可变性只是保证你的函数是纯粹的100多种方法中的一种。实际上95%的函数都是纯函数。

如果你真的没有明确的理由,你就不应该使用任何限制(比如不可变性)。如果你想“撤销”某些状态,你可以创建事务。如果你想简化通信,你可以发送带有不可变数据的事件。这取决于你。

我写这条信息来自后马克思主义共和国。我相信任何思想的激进化都是错误的。

不同的看法……

我的另一个答案是从非常实际的角度来解决这个问题的,我仍然喜欢它。我决定把这个作为另一个答案,而不是对那个答案的补充,因为它是一个无聊的哲学咆哮,希望它也回答了这个问题,但并不真正符合我现有的答案。

博士TL;

即使在小型项目中,不变性也很有用,但不要认为它的存在就是为你准备的。

答案要长得多

注意:为了这个回答的目的,我使用“纪律”这个词来表示为了某些好处而自我否定。

这在形式上类似于另一个问题:“我应该使用Typescript吗?”为什么类型在JavaScript中如此重要?”它也有类似的答案。考虑以下场景:

你是5000行JavaScript/CSS/HTML代码库的唯一作者和维护者。你半技术性的老板读了一些关于typescript的最新热点,并建议我们可能想要改用它,但把决定留给你。所以你读到它,玩它,等等。

现在你要做一个选择,你是转到Typescript?

Typescript has some compelling advantages: intellisense, catching errors early, specifying your APIs upfront, ease of fixing things when refactoring breaks them, fewer tests. Typescript also has some costs: certain very natural and correct JavaScript idioms can be tricky to model in it's not-especially-powerful type system, annotations grow the LoC, time and effort of rewriting existing codebase, extra step in the build pipeline, etc. More fundamentally, it carves out a subset of possible correct JavaScript programs in exchange for the promise that your code is more likely to be correct. It's arbitrarily restrictive. That's the whole point: you impose some discipline that limits you (hopefully from shooting yourself in the foot).

回到上一段所提到的问题:值得这样做吗?

在上述场景中,我认为,如果您非常熟悉中小型JS代码库,那么选择使用Typescript更具有美感而非实用性。这很好,美学并没有错,只是它们不一定引人注目。

场景2:

你换了工作,现在是Foo公司的一名业务线程序员。你和一个10人团队一起工作,开发90000个LoC(还在不断增加)JavaScript/HTML/CSS代码库,以及一个相当复杂的构建管道,包括babel、webpack、一套polyfills、react与各种插件、一个状态管理系统、大约20个第三方库、大约10个内部库、编辑器插件(如带有内部风格指南规则的linter)等等。

Back when you were 5k LoC guy/girl, it just didn't matter that much. Even documentation wasn't that big a deal, even coming back to a particular portion of the code after 6 months you could figure it out easily enough. But now discipline isn't just nice but necessary. That discipline may not involve Typescript, but will likely involve some form of static analysis as well as all the other forms of coding discipline (documentation, style guide, build scripts, regression testing, CI). Discipline is no longer a luxury, it is a necessity.

所有这些都适用于1978年的GOTO:你的C语言的小21点游戏可以使用GOTO和意大利面逻辑,选择你自己的冒险方式并不是什么大不了的事情,但随着程序变得越来越大,野心越来越大,那么,无序地使用GOTO就不能持续下去了。所有这些都适用于今天的不变性。

就像静态类型一样,如果你不是在一个由工程师团队维护/扩展的大型代码库上工作,那么使用不可变性的选择更多的是美观而不是实用:它的好处仍然存在,但可能还没有超过成本。

但就像所有有用的学科一样,到了一定程度,它就不再是可有可无的了。如果我想保持健康的体重,那么关于冰淇淋的纪律可能是可有可无的。但如果我想成为一名有竞争力的运动员,我对是否吃冰淇淋的选择就包含在我对目标的选择之中。如果你想用软件改变世界,不可变性可能是你避免它在自身重压下崩溃所需要的一部分。

为什么不可变性在JavaScript中如此重要(或需要)?

不可变性可以在不同的上下文中跟踪,但最重要的是根据应用程序状态和应用程序UI跟踪它。

我认为JavaScript Redux模式是非常流行和现代的方法,因为你提到了。

对于UI,我们需要让它具有可预测性。 如果UI = f(应用程序状态),这将是可预测的。

应用程序(在JavaScript中)通过使用reducer函数实现的操作来改变状态。

reducer函数只是接受动作和旧状态,并返回新状态,保持旧状态不变。

new state  = r(current state, action)

好处是:你可以穿越状态,因为所有的状态对象都被保存了,你可以在任何状态下呈现应用程序,因为UI = f(state)

所以你可以很容易地撤销/重做。


碰巧创建所有这些状态仍然可以是内存高效的,与Git类似是很好的,我们在Linux操作系统中有类似的符号链接(基于索引节点)。