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

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

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

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


当前回答

不变性的逆向观点

TL/DR:不可变性在JavaScript中更像是一种时尚趋势,而不是必须。如果你正在使用React,它确实为状态管理中一些令人困惑的设计选择提供了一个整洁的解决方案。然而,在大多数其他情况下,它带来的复杂性并不能增加足够的价值,更多的是用来填充简历,而不是满足客户的实际需求。

长话短说:阅读下文。

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

很高兴你问了这个问题!

前段时间,一个名叫Dan Abramov的天才写了一个javascript状态管理库Redux,它使用纯函数和不可变性。他还制作了一些非常酷的视频,让这个想法非常容易理解(和销售)。

时机很好。Angular的新鲜感正在消退,JavaScript世界已经准备好专注于最新的东西,它有合适的酷的程度,这个库不仅创新,而且与另一个硅谷巨头的React完美地结合在一起。

很遗憾,JavaScript的世界是由时尚主宰的。现在阿布拉莫夫被奉为半神,而我们所有的凡人都必须服从不朽之道……不管它是否有意义。

改变对象有什么错?

没有什么!

事实上,程序员一直在改变对象,呃…只要有对象就会变异。换句话说,50多年的应用程序开发。

为什么要把事情复杂化呢?当你有一个对象cat并且它死了,你真的需要第二个cat来跟踪变化吗?大多数人只会说猫。isDead =真的,结束吧。

(改变对象)难道不会让事情变得简单吗?

是的!. .当然了!

特别是在JavaScript中,它在实际应用中最有用的是呈现在其他地方维护的某些状态的视图(比如在数据库中)。

如果我有一个新的News对象需要更新怎么办?... 在这种情况下我该如何实现呢?删除商店并重新创建它?向数组中添加对象难道不是一种成本较低的操作吗?

好吧,您可以采用传统的方法并更新News对象,这样您在内存中对该对象的表示就会发生变化(以及显示给用户的视图,或者人们希望如此)……

或者……

您可以尝试迷人的FP/Immutability方法,并将对News对象的更改添加到跟踪每个历史更改的数组中,这样您就可以遍历数组并找出正确的状态表示形式(哎呀!)。

我想知道这里是什么。请启发我:)

时尚来来去去,伙计。剥猫皮的方法有很多。

很抱歉,您不得不忍受不断变化的编程范式所带来的困惑。但是,嘿,欢迎加入俱乐部!!

现在,关于不可变性,有几个重要的要点需要记住,你会以只有天真的人才能聚集起来的狂热强度向你抛出这些问题。

1)不可变性可以避免多线程环境中的竞争条件。

多线程环境(如c++, Java和c#)在多个线程想要更改对象时,会锁定对象。这对性能不利,但比数据损坏要好。然而,这并不像让一切都是不变的那样好(上帝赞美Haskell!)

但是唉!在JavaScript中,你总是在一个线程上操作。甚至是网络工作者(每个人都在独立的上下文中运行)。所以既然你不能在你的执行环境中有一个线程相关的竞态条件(所有那些可爱的全局变量和闭包),支持不可变性的主要观点就被排除在外了。

(话虽如此,在web worker中使用纯函数有一个优势,那就是你不会期望在主线程中摆弄对象。)

2)不可变性可以(以某种方式)避免应用状态中的竞态条件。

这里是问题的真正关键,大多数(React)开发者会告诉你不可变性和FP可以以某种方式创造这个魔法,让你的应用程序的状态变得可预测。

当然,这并不意味着你可以避免数据库中的竞态条件,要做到这一点,你必须协调所有浏览器中的所有用户,为此你需要一个后端推送技术,如WebSockets(下文将详细介绍),将更改广播给每个运行应用程序的人。

这也不意味着JavaScript中存在一些固有的问题,你的应用程序状态需要不可变才能变得可预测,任何在React之前编写前端应用程序的开发人员都会告诉你这一点。

This rather confusing claim simply means that if you use React your application is prone to race conditions, but that immutability allows you to take that pain away. Why? Because React is special.. its been designed first and foremost as a highly optimised rendering library with state management subverted to that aim, and thus component state is managed via an asynchronous chain of events (aka "one-way data binding") that optimize rendering but you have no control over and rely on you remembering not to mutate state directly...

在这种情况下,很容易看出对不可变性的需求与JavaScript没有多大关系,而与React有很大关系:如果在你的新应用程序中有一堆相互依赖的更改,并且没有简单的方法来确定当前的状态,你会感到困惑,因此使用不可变性来跟踪每一个历史更改是非常有意义的。

3)比赛条件非常糟糕。

如果你在用React的话,它们可能是。但如果你选择不同的框架,它们就很少出现。

除此之外,你通常还有更大的问题要处理……像依赖性这样的问题。比如一个臃肿的代码库。比如你的CSS没有被加载。比如缓慢的构建过程,或者被固定在一个单一的后端,使得迭代几乎不可能。就像没有经验的开发者不明白发生了什么,把事情搞得一团糟。

你知道的。现实。但是,谁会在乎这些呢?

4)不可变性利用引用类型来减少跟踪每个状态变化对性能的影响。

因为说真的,如果你打算在每次状态改变时复制东西,你最好确保你在这方面是聪明的。

5)不可变性允许你撤销某些内容。

因为er . .这是项目经理要求的第一个功能,对吧?

6)不可变状态与WebSockets结合有很多很酷的潜力

最后但并非最不重要的是,状态增量的积累与WebSockets结合使用是一个非常引人注目的例子,它允许将状态轻松地作为不可变事件流使用……

Once the penny drops on this concept (state being a flow of events -- rather than a crude set of records representing the latest view), the immutable world becomes a magical place to inhabit. A land of event-sourced wonder and possibility that transcends time itself. And when done right this can definitely make real-time apps easier to accomplish, you just broadcast the flow of events to everyone interested so they can build their own representation of the present and write back their own changes into the communal flow.

But at some point you wake up and realise that all that wonder and magic do not come for free. Unlike your eager colleagues, your stakeholders (yea, the people who pay you) care little about philosophy or fashion and a lot about the money they pay to build a product they can sell. And the bottom line is that its harder to code for immutability and easier to break it, plus there is little point having an immutable front-end if you don't have a back-end to support it. When (and if!) you finally convince your stakeholders that you should publish and consume events via a push techology like WebSockets, you find out what a pain it is to scale in production.


现在给你一些建议,如果你选择接受的话。

A choice to write JavaScript using FP/Immutability is also a choice to make your application code-base larger, more complex and harder to manage. I would strongly argue for limiting this approach to your Redux reducers, unless you know what you are doing... And IF you are going to go ahead and use immutability regardless, then apply immutable state to your whole application stack, and not just the client-side. After all, there is little point having an immutable front-end, and then connect it to a database where all records have a single mutable version... you just go back to the same problems you were trying to get away from!

Now, if you are fortunate enough to be able to make choices in your work, then try and use your wisdom (or not) and do what's right by the person who is paying you. You can base this on your experience, on your gut, or whats going on around you (admittedly if everyone is using React/Redux then there a valid argument that it will be easier to find a resource to continue your work).. Alternatively, you can try either Resume Driven Development or Hype Driven Development approaches. They might be more your sort of thing.

简而言之,关于不可变性,我们要说的是,它会让你在同龄人中变得时髦,至少在下一次热潮到来之前,到那时你会很高兴地离开。


现在,在这段自我治疗之后,我想指出的是,我已经在我的博客中添加了一篇文章=> JavaScript的不变性:逆向观点。如果你有强烈的感觉,也想倾诉,请在那里回复;)

其他回答

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

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

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

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

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

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

new state  = r(current state, action)

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

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


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

举个例子:

const userMessage  = { 
   user: "userId",
   topic: "topicId"
   content: {}
}

validateMessage(userMessage)
saveMessage(userMessage) 
sendMessageViaEmail(userMessage)
**sendMessageViaMobilePush(userMessage)** 

console.log(userMessage) // => ?

现在回答一些问题:

在可变代码中的sendMessageViaMobilePush(userMessage))下的userMessage是什么?

{ id: "xxx-xxx-xxx-xxx", //set by ..(Answer for question 3) user:"John Tribe", //set by sendMessageViaEmail topic: "Email title", //set by sendMessageViaEmail status: FINAL, //set by saveMessage or could be set by sendMessageViaEmail from: "..", //set by sendMessageViaEmail to:"...", //set by sendMessageViaEmail valid:true, //set by validateMessage state: SENT //set by sendMessageViaEmail } Surprised?? Me too :d. But this is normal with mutability in javascript. (in Java too but a bit in different way. When You expect null but get some object).

What is under userMessage on same line in immutable code? const userMessage = { user: "userId", topic: "topicId", content: {} } Easy right ? Can You guess by which method "id" is updated in mutable code in Snippet 1 ?? By sendMessageViaEmail. Why? Why not? Well it was at first updated by saveMessage, but then overridden by sendMessageViaEmail. In mutable code people didn't received push messages (sendMessageViaMobilePush). Can You guess why ?? because I am amazing developer :D and I put safety check in method sendMessageViaMobilePush(userMessage) function sendMessageViaMobilePush(userMessage) { if (userMessage.state != SENT) { //was set to SENT by sendMessageViaEmail send(userMessage) } } Even if You saw this method before, was this possible for You to predict this behavior in mutable code ? For me it wasn't.

希望这能帮助你理解在javascript中使用可变对象的主要问题。

请注意,当复杂性上升时,很难检查设置的内容和位置,特别是当您与他人一起工作时。

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

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

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

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

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

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

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

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

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

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是安全的,因为类的内部状态不能改变。

我认为支持不可变对象的主要原因是保持对象的状态有效。

假设我们有一个叫arr的物体。当所有项都是相同的字母时,该节点有效。

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

如果arr成为一个不可变对象,那么我们将确保arr始终处于有效状态。