为什么不可变性在JavaScript中如此重要(或需要)?不变性的逆向观点

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

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

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

如果我设置一个对象数组,最初具有一个值,我无法操作它。这就是不变性原则所说的,对吧?(如果我错了,请纠正我。但是,如果我有一个必须更新的新 News 对象,该怎么办?在通常情况下,我可以将对象添加到数组中。在这种情况下,我如何实现?删除存储并重新创建它?将对象添加到数组中不是一种成本较低的操作吗?


答案 1

我最近一直在研究同一个主题。我会尽我所能回答你的问题,并尝试分享我迄今为止学到的东西。

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

基本上,它归结为一个事实,即不可变性提高了可预测性,性能(间接)并允许突变跟踪。

可 预见性

突变隐藏了变化,从而产生(意外的)副作用,这可能导致令人讨厌的错误。在强制实施不可变性时,可以使应用程序体系结构和心智模型保持简单,从而更轻松地推理应用程序。

性能

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

所有更新都会返回新值,但内部结构是共享的,以大大减少内存使用量(和 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();

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


答案 2

不变性的逆向观点

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

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

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

好吧,我很高兴你问!

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

时机很完美。Angular的新颖性正在消退,JavaScript世界已经准备好专注于具有适当程度的冷却度的最新事物,这个库不仅具有创新性,而且与另一个硅谷巨头兜售的React完美契合。

尽管可能很可悲,但时尚在JavaScript世界中占主导地位。现在,阿布拉莫夫被誉为半神,我们所有凡人都必须让自己屈服于不变性的道......无论它是否有意义。

变异对象有什么问题?

无!

事实上,程序员一直在为er...只要有对象变异。换句话说,50多年的应用程序开发经验。

为什么要使事情复杂化?当你有对象并且它死了,你真的需要一秒钟来跟踪变化吗?大多数人只会说完并完成它。catcatcat.isDead = true

(变异对象)不是让事情变得简单吗?

是的!。。当然可以!

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

如果我有一个必须更新的新新闻对象,该怎么办?...在这种情况下,我如何实现?删除商店并重新创建它?将对象添加到数组中不是一种成本较低的操作吗?

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

或者...

您可以尝试性感的FP / Immutability方法,并将对对象的更改添加到跟踪每个历史更改的数组中,以便您可以循环访问数组并找出正确的状态表示形式(哎呀!News

我试图了解这里有什么。请启发我:)

时尚来来去去的伙伴。有很多方法可以剥猫皮。

很抱歉,您必须忍受一组不断变化的编程范式的混乱。但是,嘿,欢迎来到俱乐部!

现在,关于不变性,有几个要点要记住,你会得到这些以只有天真才能聚集的狂热强度抛给你。

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

多线程环境(如C++、Java 和 C#)在多个线程想要更改对象时会锁定对象。这对性能不利,但比数据损坏的替代方案更好。然而,这还不如让一切变得一成不变(主赞美哈斯克尔!)。

但是,唉!在JavaScript中,你总是在单个线程上运行。甚至是Web工作者(每个工作线程都在单独的上下文中运行)。因此,由于您不能在执行上下文中具有与线程相关的争用条件(所有这些可爱的全局变量和闭包),因此支持不可变性的主要观点就消失了。

(话虽如此,在Web worker中使用纯函数有一个优点,那就是你对在主线程上摆弄对象没有任何期望。

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

这是问题的真正症结所在,大多数(React)开发人员会告诉你,不可变性和FP可以以某种方式发挥这种魔力,使应用程序的状态变得可预测。

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

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

这个相当令人困惑的声明仅仅意味着,如果你使用React,你的应用程序很容易出现竞争条件,但这种不可变性可以让你消除这种痛苦。为什么?因为 React 很特别。它首先被设计为一个高度优化的渲染库,状态管理被颠覆到这个目标,因此组件状态通过一个异步事件链(又名“单向数据绑定”)进行管理,这些事件可以优化渲染,但你无法控制并依赖于你记住不要直接改变状态......

鉴于这种情况,很容易看出对不可变性的需求与JavaScript几乎没有关系,而与React有很大关系:如果你的新应用程序中有一堆相互依赖的变化,并且没有简单的方法来弄清楚你目前的状态,你会感到困惑, 因此,使用不可变性来跟踪每个历史变化是完全有意义的

3)竞争条件绝对不好。

好吧,如果你使用的是 React,它们可能是。但是,如果您选择不同的框架,则很少见。

此外,您通常有更大的问题需要处理...像依赖地狱这样的问题。就像一个臃肿的代码库。就像你的CSS没有被加载一样。就像一个缓慢的构建过程或被困在一个整体式后端,这使得迭代几乎是不可能的。就像没有经验的开发人员不了解发生了什么,把事情搞得一团糟。

你知道的。现实。但是,嘿,谁在乎呢?

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

因为说真的,如果你每次的状态发生变化时都要复制东西,你最好确保你是聪明的。

5)不变性允许您撤消内容

因为呃..这是您的项目经理将要要求的头号功能,对吧?

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

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

一旦这个概念落下了一分钱(状态是事件的流动 - 而不是代表最新观点的一组粗糙的记录),不可变的世界就变成了一个神奇的居住地。一个超越时间本身的事件来源的奇迹和可能性的土地。如果做得好,这绝对可以使实时应用程序更容易完成,你只需将事件流广播给每个感兴趣的人,这样他们就可以建立自己的当下表示,并将自己的更改写回公共流中。

但在某些时候,你醒来时会意识到,所有这些奇迹和魔法都不是免费的。与你热切的同事不同,你的利益相关者(是的,付钱给你的人)很少关心哲学或时尚,而是关心他们为制造可以销售的产品而支付的钱。最重要的是,更难为不可变性编写代码,并且更容易破坏它,而且如果你没有后端来支持它,那么拥有不可变的前端就没有多大意义。当你最终说服你的利益相关者你应该通过像WebSockets这样的推送技术来发布和使用事件时,你就会发现在生产中扩展是多么痛苦


Now for some advice, should you choose to accept it.

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, as you're missing the real value of it otherwise.

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.

In short, the thing to be said for immutability is that it will make you fashionable with your peers, at least until the next craze comes around, by which point you'll be glad to move on.


Now after this session of self-therapy I'd like to point out that I've added this as an article in my blog => Immutability in JavaScript: A Contrarian View. Feel free to reply in there if you have strong feelings you'd like to get off your chest too ;).