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

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

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

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


当前回答

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

假设我们有一个叫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始终处于有效状态。

其他回答

我已经为可变(或不可变)状态创建了一个框架不可知的开源(MIT)库,它可以取代所有那些不可变的存储,如库(redux, vuex等…)

对我来说,不可变状态是丑陋的,因为有太多的工作要做(很多简单的读/写操作),代码可读性较差,大数据集的性能不能接受(整个组件重新渲染:/)。

对于深度状态观察者,我只能用点表示法更新一个节点并使用通配符。我还可以创建状态的历史记录(撤销/重做/时间旅行),只保留那些已经改变的具体值{path:value} =较少的内存使用。

使用深度状态观测器,我可以对事情进行微调,并对组件行为进行粒度控制,因此性能可以大幅提高。代码可读性更强,重构也更容易——只需要搜索和替换路径字符串(不需要更改代码/逻辑)。

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

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

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

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

不变性的逆向观点

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

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

举个例子:

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中使用可变对象的主要问题。

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