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

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

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

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


当前回答

举个例子:

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

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

其他回答

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

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

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

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

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

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

new state  = r(current state, action)

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

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


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

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

虽然其他答案都很好,但为了解决您关于实际用例的问题(来自其他答案的评论),让我们暂时离开您的运行代码,看看您眼前无处不在的答案:git。如果每次提交都会覆盖存储库中的数据,会发生什么情况?

现在我们遇到了不可变集合面临的一个问题:内存膨胀。Git非常聪明,它不会在每次更改时简单地复制文件,而是简单地跟踪差异。

虽然我不太了解git的内部工作原理,但我只能假设它使用了与您所参考的库类似的策略:结构化共享。在底层,库使用try或其他树只跟踪不同的节点。

对于内存中的数据结构,这种策略的性能也相当好,因为有一些知名的树操作算法在对数时间内运行。

另一个用例:假设你想在你的webapp上有一个撤销按钮。对于数据的不可变表示,实现这一点相对简单。但是如果您依赖于突变,这意味着您必须担心缓存世界的状态并进行原子更新。

简而言之,运行时性能和学习曲线的不可变性是要付出代价的。但是任何有经验的程序员都会告诉您,调试时间比代码编写时间要长一个数量级。运行时性能受到的轻微影响可能被用户不必忍受的与状态相关的错误所抵消。

不变性的逆向观点

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的不变性:逆向观点。如果你有强烈的感觉,也想倾诉,请在那里回复;)

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

关于可变性

从技术角度来看,可变性并没有错。它是快速的,它是重复使用内存。开发人员从一开始就习惯了它(我记得)。可变性的使用存在问题,也会带来一些麻烦。

如果object不与任何东西共享,例如存在于函数的作用域中并且不对外公开,那么就很难看到不变性的好处。在这种情况下,不可变是没有意义的。一成不变的感觉始于某些东西被分享。

可变性头痛

可变的共享结构很容易产生许多陷阱。对访问引用的代码的任何部分的任何更改都会影响到具有此引用可见性的其他部分。这种影响将所有部分连接在一起,即使它们不应该意识到不同的模块。一个函数的突变可能会导致应用程序的完全不同部分崩溃。这样的事情是一个糟糕的副作用。

其次经常与突变的问题是损坏的状态。当突变过程中途失败,一些字段被修改,一些字段没有被修改时,就会发生损坏状态。

更重要的是,通过突变很难追踪变化。简单的参考检查不会显示出差异,要知道发生了什么变化需要做一些深入的检查。此外,为了监测变化,还需要引入一些可观察的模式。

最后,突变是信任缺失的原因。如果某个结构可以变异,你怎么能确定它有你想要的价值。

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

如上例所示,传递可变结构总是可以通过具有不同的结构来完成。函数doSomething正在改变外部给定的属性。没有对代码的信任,你真的不知道你拥有什么,你将拥有什么。所有这些问题的发生是因为:可变结构表示指向内存的指针。

不可变性与价值有关

不可变性意味着改变不是在相同的对象、结构上完成的,而是在新的对象、结构中表示的。这是因为引用不仅表示内存指针的值。每一次改变都会创造新的价值,而不会改变旧的价值。这样清晰的规则给予了信任和代码的可预测性。函数使用起来是安全的,因为它们处理的不是突变,而是具有自己值的自己的版本。

使用值而不是内存容器可以确定每个对象都表示特定的不可更改的值,并且使用它是安全的。

不可变结构表示值。

我将在一篇中型文章(https://medium.com/@macsikora/the-state-of- immutabil-169d2cd11310)中更深入地探讨这个主题