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

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

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

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


当前回答

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

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

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

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

其他回答

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

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

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

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

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

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

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

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

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

我最近也在研究同样的话题。我会尽我最大的努力回答你的问题,并尝试分享我到目前为止学到的东西。

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

基本上可以归结为这样一个事实:不可变性增加了可预测性、性能(间接地),并允许突变跟踪。

可预测性

突变隐藏了更改,而更改会产生(意想不到的)副作用,从而导致严重的bug。当您强制执行不可变性时,您可以保持应用程序架构和心理模型的简单性,这使您更容易对应用程序进行推理。

性能

尽管向不可变对象添加值意味着需要创建一个新实例,其中需要复制现有值,并且需要向新对象添加新值,这会消耗内存,但不可变对象可以利用结构共享来减少内存开销。

所有更新都返回新值,但内部结构共享给 大幅度减少内存使用(和GC抖动)。这意味着如果 你附加到一个有1000个元素的向量,它实际上并不创建 一个新的向量,长度为1001个元素。最有可能的是,内部只有几个 分配小对象。

你可以在这里阅读更多相关内容。

突变跟踪

除了减少内存使用外,不可变性还允许您通过使用引用和值相等来优化应用程序。这使得查看是否有任何更改非常容易。例如,react组件的状态变化。您可以使用shouldComponentUpdate通过比较状态对象来检查状态是否相同,并防止不必要的呈现。 你可以在这里阅读更多相关内容。

额外的资源:

不变之道 不可变数据结构和JavaScript JavaScript中的不可变性

如果我在初始值中设置一个对象数组。我不能 对它进行操作。这就是不可变原理,对吧?(正确的 如果我错了,请告诉我)。但是,如果我有一个新的News对象 被更新吗?通常情况下,我可以将对象添加到 数组中。在这种情况下我该如何实现呢?删除商店并重新创建它? 向数组中添加对象难道不是一种成本较低的操作吗?

是的,这是正确的。如果你对如何在你的应用程序中实现这一点感到困惑,我建议你看看redux是如何做到这一点的,以熟悉核心概念,它对我有很大帮助。

我喜欢使用Redux作为例子,因为它包含了不变性。它有一个单一的不可变的状态树(称为存储),其中所有的状态变化都是通过分派操作来显式的,这些操作由reducer处理,reducer接受前一个状态和所述操作(一次一个)并返回应用程序的下一个状态。你可以在这里阅读更多关于它的核心原则。

在egghead上有一门很好的redux课程。redux的作者Dan Abramov解释了这些原则如下(我修改了一些代码以更好地适应场景):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

此外,这些视频进一步详细演示了如何实现不变性:

数组 对象

不同的看法……

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

博士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就不能持续下去了。所有这些都适用于今天的不变性。

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

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

不变性的逆向观点

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

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

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

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

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