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


204

我目前正在研究React JSReact Native框架。在阅读关于Facebook的Flux和Redux实现的文章时,我遇到了Immutability或Immutable-JS库

问题是,为什么不变性如此重要?更改对象有什么问题?它不是使事情变得简单吗?

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

如果我设置说最初具有值的对象数组,我将无法对其进行操作。这就是不变性原则的意思,对不对?(如果我错了,请纠正我。)但是,如果我有一个新的News对象必须更新怎么办?在通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现?删除商店并重新创建?是不是将对象添加到数组中的开销较小?



2
不变的数据结构和纯函数导致引用透明,这使得推理程序行为变得容易得多。使用功能数据结构时,您还可以免费回溯。
WorBlux 2015年

我提供了Redux的观点@bozzmob。
prosti

1
通常,将不可渗透性作为功能范式的概念来学习,而不是试图认为JS与它有关。React是由函数式编程的爱好者编写的。您必须知道他们所知道的才能理解他们。
Gherman

这不是必需的,但确实提供了一些不错的权衡。可变状态是软件,运动部件是硬件
克里斯蒂安·杜邦

Answers:


196

我最近一直在研究同一主题。我会尽力回答您的问题,并尝试分享到目前为止我学到的知识。

问题是,为什么不变性如此重要?更改对象有什么问题?它不是使事情变得简单吗?

基本上可以归结为不变性增加了(间接)可预测性,性能和允许进行突变跟踪的事实。

可预测性

突变会隐藏变化,从而产生(意外的)副作用,这可能会导致讨厌的错误。当您强制执行不变性时,可以使您的应用程序体系结构和思维模型保持简单,这使得对应用程序的推理更加容易。

性能

即使向不可变对象添加值意味着需要在需要复制现有值的情况下创建新实例,并且需要向新对象添加新值,这会消耗内存,但不可变对象可以利用结构共享来减少内存高架。

所有更新都返回新值,但是内部结构被共享以大大减少内存使用(和GC崩溃)。这意味着,如果将一个向量附加到具有1000个元素的向量上,则实际上不会创建长度为1001个元素的新向量。内部很可能只分配了几个小对象。

您可以在此处了解更多信息。

变异追踪

除了减少内存使用量之外,不变性还允许您通过使用引用和值相等来优化应用程序。这使得查看任何更改是否真的很容易。例如,反应组件的状态变化。您可以shouldComponentUpdate通过比较状态对象来检查状态是否相同,并防止不必要的渲染。您可以在此处了解更多信息。

其他资源:

如果我设置,说一个对象数组最初带有一个值。我无法操纵它。这就是不变性原则的意思,对吗?(如果我错了,请纠正我)。但是,如果我有一个新的News对象必须更新怎么办?在通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现?删除商店并重新创建?是不是将对象添加到数组中的开销较小?

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

我喜欢以Redux为例,因为它具有不变性。它具有一个不变的状态树(称为store),其中所有状态更改都是通过调度动作来显式显示的,该动作由化简器处理,该化简器接受前一个状态以及所述动作(一次一个)并返回应用程序的下一个状态。您可以在此处阅读有关其核心原理的更多信息。

egghead.io上有一个很棒的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();

此外,这些视频进一步详细展示了如何实现以下方面的不变性:


1
@naomik感谢您的反馈!我的意图是说明这个概念,并明确表明对象没有发生突变,也不一定表明如何始终实现它。但是,我的示例可能有点混乱,我将对其进行一些更新。
danillouz

2
@bozzmob不客气!不,这是不正确的,您需要自己在reducer中强制执行不变性。这意味着您可以遵循视频中演示的策略,也可以使用immutablejs之类的库。您可以在此处此处找到更多信息。
danillouz 2015年

1
@naomik ES6 const与不变性无关。Mathias Bynens撰写了一篇很棒的博客文章
Lea Rosema'3

1
@terabaud感谢您分享链接。我同意这是一个重要的区别。^ _ ^
谢谢您

4
请说明此问题:“突变隐藏更改,这会产生(意外的)副作用,这可能会导致令人讨厌的错误。强制实施不变性时,您可以使应用程序体系结构和思维模型保持简单,从而使推理应用程序更加容易。” 因为在JavaScript上下文中根本不是这样。
巴甫莱基奇

142

不变性的相反观点

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

长答案:请阅读以下内容。

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

好吧,很高兴你问!

前一段时间,一个非常有才华的人Dan Abramov编写了一个名为Redux的javascript状态管理库,该库使用纯函数和不变性。他还制作了一些非常酷的视频,使这个想法非常容易理解(和出售)。

时机是完美的。Angular的新颖性正在逐渐消失,JavaScript世界已准备好专注于具有合适程度的最新事物,并且该库不仅具有创新性,而且与React完美地结合在一起,React由另一个硅谷强者兜售。

不幸的是,时尚在JavaScript世界中占统治地位。现在,阿布拉莫夫被誉为半神半兽,而我们所有人凡人都必须服从于不变性 ……无论它是否有意义。

更改对象有什么问题?

没有!

实际上,程序员已经将对象突变了……只要存在可以突变的对象。换句话说,超过50年的应用程序开发。

为什么使事情复杂化?当您发现对象cat并死亡时,您是否真的需要一秒钟cat来跟踪更改?大多数人只会说cat.isDead = true并完成它。

(更改对象)不是使事情简单吗?

是!..当然可以!

特别是在JavaScript中,实际上,JavaScript最有用用于呈现在其他位置(例如数据库中)维护的某些状态的视图。

如果我有一个新的News对象必须更新怎么办?...在这种情况下如何实现?删除商店并重新创建?是不是将对象添加到数组中的开销较小?

好了,您可以采用传统方法并更新News对象,因此该对象的内存表示形式会发生变化(以及向用户显示的视图,或者有人希望如此)...

或者...

您可以尝试使用性感的FP / Immutability方法,并将对News对象的更改添加到跟踪每个历史更改的数组中,以便随后遍历数组并找出正确的状态表示形式(phe!)。

我正在尝试了解这里的内容。请赐教:)

时尚来来去去哥们。有很多方法可以给猫皮。

抱歉,您不得不承受一套不断变化的编程范例的困惑。但是,嘿,欢迎来到俱乐部!

现在要牢记关于不变性的两个重要要点,您将以只有天真才能发扬的狂热强度将它们扔给您。

1)不可变性对于避免多线程环境中的竞争条件非常有用

当多个线程想要更​​改对象时,多线程环境(如C ++,Java和C#)会犯锁定对象的做法。这对性能不利,但比替代数据损坏更好。但是还不如使所有东西都变得不可变(上帝赞美Haskell!)。

可惜!在JavaScript中,您总是在单个线程上操作。甚至是网络工作者(每个人都在单独的上下文中运行)。因此,由于您在执行上下文(所有可爱的全局变量和闭包)中无法拥有与线程相关的竞争条件,因此支持不可变性的要点不在窗外。

(话虽如此,有一个优势,在网络工作者,这是,你有没有关于与主线程上的对象摆弄的预期使用纯函数。)

2)不变性可以(以某种方式)避免应用状态下的竞争情况。

这才是真正的症结所在,大多数(反应)开发人员都会告诉您,不变性和FP可以某种方式发挥作用,从而使应用程序的状态变得可预测。

当然,这并不意味着您可以避免数据库中出现争用情况,要想摆脱这种情况,就必须协调所有浏览器中的所有用户,为此,您需要像WebSockets这样的后端推送技术(详情请参见下文),它将向运行该应用程序的每个人广播更改。

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

这个相当令人困惑的说法只是意味着使用React,您的应用程序状态将变得更容易出现竞争状况,但是不变性可以使您减轻痛苦。为什么?因为React是特殊的..它被设计为高度优化的渲染库,其连贯状态管理位居第二,因此,组件状态通过您无法控制的异步事件链(也称为“单向数据绑定”)进行管理结束并依靠你记住不要直接改变状态 ...

在这种情况下,很容易看出对不变性的需求与JavaScript无关,而与React中的竞争条件有很大关系:如果您的应用程序中有许多相互依赖的更改,并且没有简单的方法来找出原因您目前处于状态,您会感到困惑,因此使用不变性来跟踪每一个历史变化都是很有意义的

3)比赛条件绝对不好。

好吧,如果您使用的是React,那么可能就是这样。但是,如果您选择其他框架,它们很少见。

此外,您通常要处理更大的问题 ……诸如依赖地狱之类的问题。就像a肿的代码库。就像您的CSS无法加载一样。就像缓慢的构建过程或卡在整体后端上,使得几乎不可能进行迭代。就像没有经验的开发人员一样,他们也不了解正在发生的事情并弄乱了事情。

你懂。现实。但是,嘿,谁在乎呢?

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

因为认真的说,如果您每次状态更改时都打算复制内容,那么最好确保您对此有所了解。

5)不变性使您可以撤消东西

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

6)不可变状态与WebSocket结合具有很大的潜力

最后但并非最不重要的一点是,与WebSocket结合使用时,状态增量的累积成为了一个令人信服的案例,它允许将状态作为不可变事件流轻松消耗...

一旦一分钱都落在了这个概念上(状态是事件的流转,而不是代表最近观点的原始记录集),不可变的世界将成为一个神奇的居住地。一个源于事件的奇妙和可能性的土地,它超越了时间本身。并在完成后右这个绝对可以让实时应用程式EASI 来完成,你只播事件给大家感兴趣,所以他们的流动建立自己表示本和自己的变化写回社区流动。

但是到了某个时刻,您醒来并意识到,所有的奇观和魔力都不是免费的。与您渴望的同事不同,您的利益相关者(是的,付钱给您的人)很少关心哲学或时尚,而关心他们为打造可销售产品而付出的金钱。最重要的是,它更难编写不可变的代码,更容易破坏它,而且如果没有后端来支持不可变的前端,那就毫无意义。当(如果!!)最终使您的利益相关者相信您应该通过WebSockets之类的推送技术发布和使用事件时,您将发现在生产中进行扩展会带来什么痛苦


现在寻求一些建议,您是否应该选择接受它。

使用FP / Immutability编写JavaScript的选择也是使应用程序代码库更大,更复杂且更难管理的选择。我强烈建议将这种方法限制在您的Redux reducer上,除非您知道自己在做什么...而且如果您要继续使用不可变性,无论如何,然后将不可变状态应用于整个应用程序堆栈,而不仅限于客户端,否则您将失去它的真正价值。

现在,如果您有幸能够在工作中做出选择,那么请尝试(或不使用)您的智慧,并付钱给您的人做对的事情。您可以根据自己的经验,内心或周围发生的事情(当然,如果每个人都在使用React / Redux,那么会有一个有效的论据,那就是找到资源来继续您的工作会更容易)。您可以尝试“ 恢复驱动开发”或“ 炒作驱动开发”方法。它们可能更像您的事情。

总之,可以说对于不变性的事情是,它令你的时尚与您同行,至少直到下一个热潮来临时,由此时你会很高兴地前进。


现在,在本次自我治疗之后,我想指出的是,我已将其作为文章添加到我的博客中=> JavaScript中的不变性:反向视图。如果您有强烈的感觉,也可以在这里回复;)。


10
你好史蒂文,是的。当我考虑immutable.js和redux时,我有所有这些疑问。但是,您的答案是惊人的!它增加了很多价值,感谢您解决我所怀疑的每个问题。即使在不可变对象上工作了几个月后,现在也变得更加清晰/更好。
bozzmob

5
我已经将React与Flux / Redux一起使用了两年多了,我完全同意您的看法,反应很好!
巴甫莱基奇

6
我非常怀疑不变性的观点与团队和代码库的大小有很好的关联,而且主要支持者是硅谷巨头也不是巧合。话虽如此,我谨对此表示不同意:不变性是有用的规则,就像不使用goto是有用的规则一样。或单元测试。或TDD。还是静态类型分析。并不意味着您每次都在做它们(尽管有些人会做)。我还要说效用与炒作是正交的:在有用/多余和性感/无聊的矩阵中,每种都有很多例子。"hyped" !== "bad"
杰瑞德·史密斯

4
嗨,@ ftor,好点,是把事情推向了另一个方向。但是,由于这里有很多关于“ JavaScript中的不可变性”的文章和争论,我觉得我需要平衡一下。因此,新手有相反的观点来帮助他们做出两种判断。
史蒂文·德·萨拉斯

4
内容翔实,标题鲜明。在找到答案之前,我一直以为我是唯一持类似观点的人。我认识到不变性的价值,但令我困扰的是,它已成为一种压迫其他技术的 教条(例如,损害了2向绑定,这对于像在KnockoutJS中实现的输入格式是极其有用的)。
Tyblitz

53

问题是,为什么不变性如此重要?更改对象有什么问题?它不是使事情变得简单吗?

实际上,事实恰恰相反:可变性使事情变得更复杂,至少从长远来看。是的,它使您的初始编码更加容易,因为您可以随心所欲地进行更改,但是当程序变大时,这将成为一个问题–如果更改了值,则更改了什么?

当您使所有内容不变时,这意味着数据再也不会因意外而更改。您可以肯定地知道,如果将值传递给函数,则无法在该函数中对其进行更改。

简而言之:如果您使用不可变的值,那么就很容易就可以对代码进行推理:每个人都会获得数据的唯一*副本,因此它无法处理并破坏代码的其他部分。想象一下,这使得在多线程环境中工作变得容易得多!

注意1:根据您所做的操作,不变性可能会带来性能损失,但是Immutable.js之类的东西会尽可能地优化。

注意2:在不太确定的情况下,您不确定Immutable.js和ES6的const含义完全不同。

在通常情况下,我可以将对象添加到数组中。在这种情况下我该如何实现?删除商店并重新创建?是不是将对象添加到数组中的开销较小?PS:如果该示例不是解释不变性的正确方法,请让我知道什么是正确的实际示例。

是的,您的新闻示例非常好,您的推理也完全正确:您不能仅修改现有列表,因此需要创建一个新列表:

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

1
我不同意这个答案,但是没有解决他的“我想从实际例子中学习”的部分问题。有人可能会说,对多个领域中使用的新闻标题列表的单一引用是一件好事。“我只需要更新列表一次,引用新闻列表的所有内容都可以免费更新”-我认为更好的答案将解决他提出的常见问题,并显示出使用不变性的有价值的替代方案。
谢谢您

1
我很高兴这个答案很有帮助!关于您的新问题:不要试图对系统进行猜测:)在这种情况下,称为“结构共享”的东西可以大大减少GC的抖动-如果列表中有10,000个项目并增加了10个,我相信这是不变的。 js将尽力重用以前的结构。让Immutable.js担心内存问题,并有机会发现它更好。
TwoStraws 2015年

6
Imagine how much easier this makes working in a multi-threaded environment!->对于其他语言也可以,但这在单线程JavaScript中不是优势。
史蒂文·德·萨拉斯

1
@StevendeSalas指出JavaScript主要是异步的和事件驱动的。它根本无法抵抗比赛条件。
贾里德·史密斯

1
@JaredSmith仍然是我的观点。FP和Immutability是避免多线程环境中的数据损坏和/或资源锁定的强大有用范例,但在JavaScript中则不是这样,因为它是单线程的。除非我缺少一些智慧的圣块,否则这里的主要权衡是您是否准备为了避免竞争而准备使代码更复杂(和更慢)……这比大多数人要少的多认为。
史蒂文·德·萨拉斯

37

尽管其他答案很好,但是要解决有关实际用例的问题(根据其他答案的注释),请让您离开正在运行的代码一分钟,然后看看您鼻子下无处不在的答案:git。如果每次您推送一次提交都覆盖了存储库中的数据,将会发生什么?

现在,我们讨论不可变集合面临的问题之一:内存膨胀。Git很聪明,不会在每次更改时都简单地创建文件的新副本,它只是跟踪差异

虽然我对git的内部运作了解不多,但我只能假设它使用与您所引用的库类似的策略:结构共享。在后台,库使用try或其他树来仅跟踪不同的节点。

这种策略对于内存中的数据结构也相当合理,因为有众所周知的对数时间运算的树运算算法。

另一个用例:假设您要在Web应用程序上使用撤消按钮。使用数据的不可变表示形式,实现这种过程相对简单。但是,如果您依赖突变,则意味着您必须担心缓存世界状况并进行原子更新。

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


1
我说的一个很好的例子。我对不变性的理解现在更加清晰。谢谢贾里德。实际上,其中一种实现是UNDO按钮:D并且您使我的工作变得非常简单。
bozzmob 2015年

3
仅仅因为git中有意义的模式并不意味着在任何地方都有意义。在git中,您实际上关心的是存储的所有历史记录,并且希望能够合并不同的分支。在前端,您不需要关心大多数状态历史记录,也不需要所有这些复杂性。
滑雪

2
@Ski只是很复杂,因为它不是默认值。我通常在我的项目中不使用mori或immutable.js:我总是很犹豫要接受第三方部门。但是,如果这是默认设置(至少是clojurerescript)或至少具有选择加入的本机选项,那么我将一直使用它,因为当我编写clojure程序时,我不会立即将所有内容填充到原子中。
贾里德·史密斯

乔·阿姆斯特朗(Joe Armstrong)表示不用担心性能,只需等待几年,摩尔定律就会为您解决。
ximo

1
@JaredSmith是的,事情变得越来越小,资源越来越有限。我不确定这是否会成为JavaScript的限制因素。我们一直在寻找提高性能的新方法(例如,斯维尔特)。顺便说一句,我完全同意你的其他评论。使用不可变数据结构的复杂性或困难通常归因于该语言没有内置支持该概念的语言。Clojure使不变性变得简单,因为它被嵌入到语言中,整个语言都是围绕这个想法而设计的。
ximo

8

问题是,为什么不变性如此重要?更改对象有什么问题?它不是使事情变得简单吗?

关于变异

从技术的角度来看,可变性没有错。速度很快,正在重新使用内存。开发人员从一开始就习惯了它(我记得它)。在使用可变性方面存在问题,并且这种使用可能带来麻烦。

如果对象不共享任何东西,例如存在于函数范围内并且没有暴露于外部,那么很难看到不变性带来的好处。确实在这种情况下,保持不变是没有意义的。当共享某些东西时,就开始具有不变性。

变异性头痛

可变的共享结构很容易造成很多陷阱。可以访问参考的代码任何部分的任何更改都会影响具有此参考可见性的其他部分。这种影响将所有部分连接在一起,即使他们不应该知道不同的模块也是如此。一个功能的变更可能会使应用程序的不同部分崩溃。这样的事情是不好的副作用。

接下来的突变问题通常是损坏的状态。当中间的突变过程失败时,可能会发生损坏状态,并且某些字段已被修改而有些字段未被修改。

而且,随着突变的发生,很难追踪变化。简单的参考检查不会显示差异,要知道需要进行哪些深度检查,就需要进行哪些更改。另外,为了监视更改,需要引入一些可观察的模式。

最后,变异是信任缺失的原因。如果可以突变,如何确定某些结构具有想要的值。

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

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

不变性与价值有关

不变性意味着更改不会在相同的对象,结构上进行,而是以新的形式表示。这是因为引用不仅代表内存指针,还代表值。每次更改都会创造新的价值,并且不会触及旧的价值。这样清晰的规则可以使信任度和代码可预测性恢复。使用函数是安全的,因为它们可以使用自己的值而不是突变来代替突变。

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

不变的结构代表着价值。

我正在中篇文章中进一步探讨该主题-https: //medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

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

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

我将JavaScript Redux模式视为一种非常时尚和现代的方法,因为您提到了这一点。

对于UI,我们需要使其可预测。如果可以预测的话UI = f(application state)

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

reducer函数仅获取动作和旧状态并返回新状态,从而保持旧状态不变。

new state  = r(current state, action)

在此处输入图片说明

好处是:由于所有状态对象均已保存,因此您可以遍历状态,并且自 UI = f(state)

因此,您可以轻松地撤消/重做。


碰巧创建所有这些状态仍然可以提高内存效率,与Git的类比很棒,我们在Linux OS中也使用符号链接(基于inode)进行了类似的类比。


5

Java不变性的另一个好处是它减少了时间耦合,这通常对设计具有实质性的好处。考虑对象的接口有两种方法:

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是不变的,那么这不再是问题。可以安全地假定我们可以调用bazbar以任何顺序调用,因为类的内部状态无法更改。


4

曾几何时,线程之间的数据同步存在问题。这个问题让人非常痛苦,有10多种解决方案。有些人试图从根本上解决它。这是函数式编程诞生的地方。就像马克思主义一样。我不明白Dan Abramov是如何将这个想法出售给JS的,因为它是单线程的。他是个天才。

我可以举一个小例子。__attribute__((pure))gcc中有一个属性。如果不特别清除函数,编译器将尝试解决您的函数是否纯净。即使状态是可变的,您的函数也可以是纯函数。不变性只是保证您的功能纯正的100多种方法之一。实际上,您的函数中有95%是纯函数。

如果您实际上没有认真的理由,则不应使用任何限制(例如不变性)。如果要“撤消”某些状态,则可以创建事务。如果您想简化通信,则可以发送不可变数据的事件。它是由你决定。

我正在写后马克思主义共和国的这封信。我敢肯定,任何想法的激进都是错误的方式。


第三段非常有意义。谢谢你 “如果您想“撤消”某些状态,则可以创建交易”!
bozzmob

顺便说一下,OOP也可以与马克思主义进行比较。还记得Java吗?哎呀,JavaScript中Java的奇数位?炒作永远不会是好事,它会引起自由基和两极分化。从历史上看,OOP比Facebook对Redux的宣传要大得多。尽管他们确实尽了最大的努力。
ximo

4

一个不同的...

我的其他答案从非常实际的角度解决了这个问题,我仍然喜欢它。我决定将此添加为另一个答案,而不是对该附录进行补充,因为这是一种无聊的哲学rant语,希望它也可以回答该问题,但实际上与我现有的答案不符。

TL; DR

即使在小型项目中,不变性也很有用,但不要因为它的存在而假定它对您有用。

答案长得多

注意:出于此答案的目的,我使用“纪律”一词来表示自我克制,以获得某些好处。

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

您是大约5000行的JavaScript / CSS / HTML代码库的唯一作者和维护者。您的半技术老板会读一些有关Typescript的新知识,并建议我们可能要改用它,但由您决定。因此,您可以阅读,玩耍等等。

因此,现在您可以选择了,您要使用Typescript吗?

Typescript具有一些引人注目的优势:智能感知,及早发现错误,预先指定您的API,在重构破坏它们时易于修复问题,减少测试。Typescript也要付出一些代价:在不是特别强大的类型系统中,某些非常自然且正确的JavaScript习惯很难建模,注释会增加LoC,重写现有代码库的时间和精力,以及构建管道中的额外步骤等。从根本上讲,它会分割出可能的正确JavaScript程序的子集,以换取您的代码更可能正确的承诺。这是任意限制的。这就是重点:您强加了一些限制自己的纪律(希望能避免射击自己的脚)。

回到上面段落中改写的问题:值得吗?

在所描述的场景中,我认为如果您对中小型JS代码库非常熟悉,那么选择使用Typescript会比实际更美观。这就是罚款,没有什么与美学,他们只是不一定引人注目。

方案B:

您现在换工作,现在是Foo Corp.的业务部门程序员。您正在与10个人组成的团队合作,在90000 LoC(并在计算中)JavaScript / HTML / CSS代码库中,并且构建流程非常复杂,涉及babel,webpack ,一组polyfill,与各种插件,状态管理系统,约20个第三方库,约10个内部库,编辑器插件(如带有内部样式指南规则的linter)等互动。

当您还是5k LoC男/女时,这没什么大不了的。即使文档是不是那个什么大不了的事,即使回来6个月后的代码的特定部分,你可以算出它很轻松了。但是现在纪律不仅很好而且很有必要。这门学科可能不涉及打字稿,但可能涉及某种形式的静态分析,以及所有其他形式的编码规则(文档,风格指南,构建脚本,回归测试,CI)的。纪律不再是一种奢侈,而是必需品

所有这些都适用GOTO于1978年:您用C语言编写的小巧的二十一点游戏可以使用GOTOs和意大利面条逻辑,选择自己的冒险方式并没有什么大不了的,但是随着程序的发展,更雄心勃勃的,善于的,无纪律的使用GOTO无法持续。所有这些今天都适用于不变性。

就像静态类型一样,如果您不使用由一组工程师维护/扩展的大型代码库,则使用不变性的选择比实际更美观:它的好处仍然存在,但可能还不超过成本。

但是,与所有有用的学科一样,它不再是可选的。如果我想保持健康的体重,那么可以选择不加冰淇淋。但是,如果我想成为一名竞技运动员,那么我是否选择吃冰淇淋取决于我选择的目标。如果您想用软件来改变世界,那么不变性可能就是避免它因自身重量而崩溃的必要条件。


1
+1我喜欢。关于Jared的更多信息。然而,不变性并不能挽救一支缺乏纪律的团队。😉
史蒂芬德萨拉斯

@StevendeSalas是一种纪律。因此,我认为它与其他形式的软件工程学科相关(但不能替代)。它是补充而不是替代。但是,正如我在对您的答案的评论中所说的那样,我不感到惊讶,这是由科技巨头推动的,他们是一群工程师,他们全都在同一个巨大的代码库上努力奋斗:)他们需要他们能获得的所有学科。大多数情况下,我不变异对象,也不使用任何形式的强制执行,因为好吧,这只是我一个人。
Jared Smith

0

我为可变(或不可变)状态创建了一个与框架无关的开源(MIT)库,它可以替换所有不可变的存储,例如lib(redux,vuex等)。

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

使用deep-state-observer,我只能使用点表示法更新一个节点并使用通配符。我还可以创建状态历史记录(撤消/重做/时间旅行),仅保留已更改的具体值{path:value}=减少内存使用量。

使用深度状态观察器,我可以微调事物,并且可以控制组件的行为,因此可以大大提高性能。代码更具可读性,重构也容易得多-只需搜索和替换路径字符串(无需更改代码/逻辑)。


-1

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

假设我们有一个名为的对象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始终处于有效状态。


我认为arr每次您致电时都会发生变异fillWithZ
rodrigo-silveira

如果使用immutable.js,则每次更改对象都会获得该对象的新副本。因此原始对象保持不变
bedorlan '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.