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

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

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

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

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

Mandy神乐2020/03/11 10:56:05

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

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

Gil梅2020/03/11 10:56:05

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

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

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

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

Pro宝儿2020/03/11 10:56:05

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以任何顺序调用,因为类的内部状态无法更改。

Tom阳光2020/03/11 10:56:05

Why is immutability so important(or needed) in JavaScript?

Immutability can be tracked in different contexts, but most important would be to track it against the application state and against the application UI.

I will consider the JavaScript Redux pattern as very trendy and modern approach and because you mentioned that.

For the UI we need to make it predictable. It will be predictable if UI = f(application state).

Applications (in JavaScript) do change the state via actions implemented using the reducer function.

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

new state  = r(current state, action)

在此处输入图片说明

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

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


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

宝儿Harry2020/03/11 10:56:05

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

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

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

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

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

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

Tom十三2020/03/11 10:56:05

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

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

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

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

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

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

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

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

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