为什么不变性在 JavaScript 中如此重要(或需要)?
Why is immutability so important (or needed) in JavaScript?
我目前正在研究 React JS and React Native frameworks. On the half way road I came across Immutability or the Immutable-JS library,当时我正在阅读有关 Facebook 的 Flux 和 Redux 实现的文章。
问题是,为什么不变性如此重要?变异 objects 有什么问题?这不是让事情变得简单吗?
举个例子,让我们考虑一个简单的 新闻 reader 应用程序,其打开屏幕是新闻标题的列表视图。
如果我设置说一个 array of objects with a value initially 我无法操纵它。这就是不变性原则所说的,对吧? (如果我错了,请纠正我。)
但是,如果我有一个必须更新的新新闻 object 怎么办?通常情况下,我可以将 object 添加到数组中。
在这种情况下我该如何实现?删除商店并重新创建它?
向数组添加 object 不是更便宜的操作吗?
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
实际上,事实恰恰相反:可变性使事情变得更加复杂,至少在长期 运行 中是这样。是的,它使您的初始编码更容易,因为您可以随心所欲地更改内容,但是当您的程序变得更大时,它就会成为一个问题 – 如果一个值发生了变化,是什么改变了它?
当你让一切都变得不可变时,这意味着数据不能再被突然改变。你肯定知道,如果你将一个值传递给一个函数,它就不能在那个函数中被改变。
简而言之:如果您使用不可变值,就可以很容易地推断出您的代码:每个人都会得到一份您数据的唯一*副本,因此它不会乱用它并破坏您代码的其他部分.想象一下,这会使在多线程环境中工作变得多么容易!
注 1:根据您的操作,不变性可能会产生性能成本,但 Immutable.js 之类的东西会尽可能优化。
注 2:在不太可能的情况下,您不确定,Immutable.js 和 ES6 const
的意思完全不同。
In usual case, I could have just added the object to the array. How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation? PS: If the example is not the right way to explain immutability, please do let me know what's the right practical example.
是的,你的新闻例子非常好,你的推理是完全正确的:你不能只修改现有列表,所以你需要创建一个新列表:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
我最近一直在研究同一主题。我会尽力回答您的问题,并尝试分享我目前学到的知识。
The question is, why is immutability so important? What is wrong in
mutating objects? Doesn't it make things simple?
基本上归结为不变性增加了可预测性、性能(间接)并允许进行突变跟踪。
可预测性
突变隐藏了变化,这会产生(意想不到的)副作用,这可能会导致严重的错误。当您强制执行不变性时,您可以使您的应用程序架构和心智模型保持简单,从而更容易推理您的应用程序。
性能
尽管向不可变对象添加值意味着需要创建一个新实例,其中需要复制现有值,并且需要将新值添加到新对象中,这会消耗内存,但不可变对象可以利用结构共享以减少内存开销。
All updates return new values, but internally structures are shared to
drastically reduce memory usage (and GC thrashing). This means that if
you append to a vector with 1000 elements, it does not actually create
a new vector 1001-elements long. Most likely, internally only a few
small objects are allocated.
您可以阅读更多相关内容 here。
变异追踪
除了减少内存使用外,不变性还允许您通过使用引用和值相等来优化您的应用程序。这使得查看是否有任何更改变得非常容易。例如,反应组件中的状态变化。您可以使用 shouldComponentUpdate
通过比较状态对象来检查状态是否相同,并防止不必要的渲染。
您可以阅读更多相关信息 here.
其他资源:
If I set say an array of objects with a value initially. I can't
manipulate it. That's what immutability principle says, right?(Correct
me if I am wrong). But, what if I have a new News object that has to
be updated? In usual case, I could have just added the object to the
array. How do I achieve in this case? Delete the store & recreate it?
Isn't adding an object to the array a less expensive operation?
是的,这是正确的。如果您对如何在您的应用程序中实现这一点感到困惑,我建议您查看 redux 如何做到这一点以熟悉核心概念,它对我帮助很大。
我喜欢以 Redux 为例,因为它支持不变性。它有一个单一的不可变状态树(称为 store
),其中所有状态更改都是通过调度操作显式进行的,这些操作由接受先前状态以及所述操作(一次一个)和 returns 应用程序的下一个状态。您可以阅读更多关于它的核心原则 here.
redux的作者egghead.io where Dan Abramov上有一个很好的redux课程,对这些原理的解释如下(我稍微修改了代码以更好地适应场景):
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();
此外,这些视频更详细地演示了如何实现不变性:
虽然其他答案很好,但为了解决您关于实际用例的问题(来自对其他答案的评论),让我们跳出您的 运行 代码一分钟,看看无处不在的答案吧在你眼皮子底下:git。如果每次推送提交时都覆盖 存储库中的数据会怎样?
现在我们要解决不可变集合面临的问题之一:内存膨胀。 Git 足够聪明,不会在您每次进行更改时简单地制作新的文件副本,它只是跟踪差异。
虽然我不太了解 git 的内部工作原理,但我只能假设它使用与您引用的库类似的策略:结构共享。在幕后,库使用 tries 或其他树来仅跟踪不同的节点。
此策略对于内存数据结构也具有合理的性能,因为 well-known 树运算算法以对数时间运行。
另一个用例:假设你想在你的网络应用程序上有一个撤销按钮。使用不可变的数据表示,实现这些相对微不足道。但是如果你依赖于变异,那就意味着你必须担心缓存世界状态和进行原子更新。
简而言之,运行时性能和学习曲线的不变性是要付出代价的。但是任何有经验的程序员都会告诉您,调试时间比编写代码的时间要多出一个数量级。对运行时性能的轻微影响可能被您的用户不必忍受的与状态相关的错误所抵消。
Why is immutability so important(or needed) in JavaScript?
可以在不同的上下文中跟踪不变性,但最重要的是根据应用程序状态和应用程序跟踪它 UI。
我会认为 JavaScript Redux 模式是非常时尚和现代的方法,因为你提到了这一点。
对于UI,我们需要使其可预测。
如果UI = f(application state)
.
是可以预见的
应用程序(在 JavaScript 中)确实通过使用 reducer 函数.
实现的操作来更改状态
reducer 函数简单地接受动作和旧状态,returns 新状态,保持旧状态不变。
new state = r(current state, action)
好处是:您 time-travel 状态,因为所有状态对象都已保存,并且您可以在任何状态下呈现应用程序,因为 UI = f(state)
所以你可以undo/redo轻松。
碰巧创建所有这些状态仍然可以节省内存,与 Git 的类比很好,我们在 Linux OS 中有类似的类比符号链接(基于索引节点)。
不变性的逆向观点
TL/DR:不变性在 JavaScript 中更像是一种时尚趋势,而不是必需品。如果您使用的是 React,它确实为某些 confusing design choices in state management. However in most other situations it wont add enough value over the complexity it introduces, serving more to pad up a resume 提供了一种巧妙的解决方法,而不是满足实际客户的需求。
长答案:阅读下文。
Why is immutability so important(or needed) in javascript?
好吧,很高兴你提出这个问题!
前段时间,一个非常有才华的人 Dan Abramov wrote a javascript state management library called Redux which uses pure functions and immutability. He also made some really cool videos 让这个想法很容易理解(和销售)。
时机非常完美。 Angular was fading, and JavaScript world was ready to fixate on the latest thing that had the right degree of cool, and this library was not only innovative but slotted in perfectly with React which was being peddled by another Silicon Valley powerhouse.
的新意
时尚在 Java 脚本的世界中占据统治地位,这可能令人难过。现在阿布拉莫夫被誉为半神,我们所有凡人都必须服从 Dao of Immutability... 无论是否有意义。
What is wrong in mutating objects?
没有!
事实上,程序员一直在改变对象……只要有对象要改变。 50+ years 换句话说,应用程序开发。
为什么要把事情复杂化?当你有对象 cat
并且它死了,你真的需要第二个 cat
来跟踪变化吗?大多数人只会说 cat.isDead = true
并完成它。
Doesn't (mutating objects) make things simple?
是的! .. 当然可以!
特别是在 JavaScript 中,它在实践中最有用的是用于呈现在其他地方(例如在数据库中)维护的某些状态的视图。
What if I have a new News object that has to be updated? ... How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation?
好吧,您可以采用传统方法并更新 News
对象,这样您在内存中对该对象的表示就会发生变化(以及向用户显示的视图,或者人们希望如此)。 .
或者……
您可以尝试性感的 FP/Immutability 方法并将您对 News
对象的更改添加到 跟踪每个历史更改的数组中 这样您就可以迭代通过数组并找出正确的状态表示应该是什么(呸!)。
I am trying to learn what's right here. Please do enlighten me :)
时尚来来去去,伙计。给猫剥皮的方法有很多种。
很抱歉,您不得不忍受一套不断变化的编程范式带来的困惑。但是,嘿,欢迎加入俱乐部!!
关于不可变性,现在要记住几个要点,你会以只有天真才能鼓起的狂热强度向你抛出这些。
1) 不变性对于避免 race conditions in multi-threaded environments.
非常有用
多线程环境(如 C++、Java 和 C#)在多个线程想要更改对象时会锁定对象。这对性能不利,但比数据损坏的替代方案要好。还不如让一切都变得不可变(赞美主Haskell!)。
但是,唉!在 JavaScript 中你总是 operate on a single thread. Even web workers (each runs inside a separate context)。因此,由于您的执行上下文中不能有 线程相关的 竞争条件(所有那些可爱的全局变量和闭包),所以支持不可变性的要点是 window.
(话虽如此, 在 Web Worker 中使用纯函数有一个优势,那就是您不会期望在主线程上摆弄对象。 )
2) 不变性可以(以某种方式)避免应用状态中的竞争条件。
这才是问题的真正症结所在,大多数 (React) 开发人员会告诉您,不可变性和 FP 可以以某种方式发挥这种魔力,让您的应用程序的状态变得可预测。
当然,这并不意味着您可以避免 race conditions in the database, to pull that one off you’d have to coordinate all users in all browsers, and for that you’d need a back-end push technology like WebSockets(更多内容见下文),它将向 运行 应用程序的每个人广播更改。
也不意味着 JavaScript 中存在一些固有问题,您的应用程序状态需要不变性才能变得可预测,任何在 React 之前编写前端应用程序的开发人员都会告诉您这一点.
这个相当令人困惑的说法只是意味着 如果你使用 React,你的应用程序很容易出现竞争条件,但不可变性可以让你消除这种痛苦。为什么?因为 React 很特别.. 它首先被设计为 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 的关系很大:如果你的 new 中有一堆相互依赖的变化应用程序并没有简单的方法来弄清楚你当前的状态,you are going to get confused,因此 使用不可变性来跟踪每个历史变化是非常有意义的。
3) 比赛条件绝对糟糕。
嗯,如果您使用的是 React,它们可能是。但如果你选择不同的框架,它们就很少见了。
此外,您通常有更大的问题需要处理……诸如依赖地狱之类的问题。就像一个臃肿的代码库。就像您的 CSS 没有加载一样。就像一个缓慢的构建过程或被困在一个单一的后端,这使得迭代几乎不可能。就像没有经验的开发者不理解发生了什么,把事情搞得一团糟。
你知道的。现实。但是,嘿,谁在乎这个?
4) 不变性利用 Reference Types 来减少跟踪每个状态变化对性能的影响。
因为严重的是,如果每次状态发生变化时你都要复制东西,你最好确保你对此很聪明。
5) 不变性允许你撤消内容。
因为呃..这是您的项目经理要求的第一个功能,对吧?
6) 不可变状态与 WebSockets 结合有很多很酷的潜力
最后但同样重要的是,状态增量的累积与 WebSockets 相结合是一个非常引人注目的案例,它允许轻松使用 state as a flow of immutable events...
一旦在这个概念上投入一分钱(状态是事件流 -- 而不是代表最新观点的粗略记录集),不变的世界就变成了一个神奇的世界居住的地方。一片土地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的当下,将自己的改变写回公共流程。
但在某个时候你醒来,意识到所有的奇迹和魔法 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。
现在给点建议,你是不是应该选择接受。
选择使用 FP/Immutability 编写 Java 脚本也是使您的应用程序代码库更大、更复杂且更难管理的选择。我强烈主张将这种方法限制在你的 Redux 减速器上,除非你知道你在做什么......如果你要继续使用不可变性,那么应用 immutable state to your whole application stack,而不仅仅是客户端-一方面,否则你会错过它的真正价值。
现在,如果您有幸能够在工作中做出选择,那么请尝试使用您的智慧(或不智慧)和 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 方法。他们可能更适合你。
简而言之,关于不变性,要说的是它 将 让你在同龄人中变得时尚,至少在下一次热潮到来之前,到那时你'会很高兴继续前进。
现在,在本次自我治疗之后,我想指出,我已将此作为文章添加到我的博客中 => Immutability in JavaScript: A Contrarian View。如果您有强烈的感觉也想发泄一下,请随时在那里回复 ;)。
我认为支持不可变对象的主要原因是保持对象的状态有效。
假设我们有一个名为 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 始终处于有效状态。
不变性在 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
要弄清楚,您需要仔细检查 class 内部结构,因为检查 public 接口并不能立即看出这一点。在具有大量可变状态和 classes.
的大型代码库中,此问题可能会爆发
如果 Foo
是不可变的,那么这就不再是问题了。可以安全地假设我们可以按任何顺序调用 baz
或 bar
,因为 class 的内部状态无法更改。
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
关于可变性
从技术角度来看,可变性没有错。它很快,它正在重新使用内存。开发人员从一开始就习惯了(我记得)。可变性的使用存在问题,这种使用可能带来麻烦。
如果对象不与任何东西共享,例如存在于函数范围内并且不暴露给外部,那么很难看到不变性的好处。实际上,在这种情况下,不可变是没有意义的。当某些东西被共享时,不变性的感觉就开始了。
变异性头痛
可变共享结构很容易造成许多陷阱。访问引用的代码的任何部分的任何更改都会影响具有此引用可见性的其他部分。这种影响将所有部分连接在一起,即使它们不应该意识到不同的模块。一个函数中的突变可能会导致应用程序的不同部分崩溃。这种东西是不好的副作用。
下一个经常发生的突变问题是损坏的状态。当变异过程在中间失败时,可能会发生损坏的状态,一些字段被修改而一些字段没有被修改。
更重要的是,随着变异很难追踪变化。简单的参考检查不会显示差异,要了解更改的内容需要进行一些深度检查。此外,为了监控变化,需要引入一些可观察的模式。
最后,突变是信任赤字的原因。如果可以变异,您如何确定某些结构具有所需的价值。
const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }
如上例所示,传递可变结构总是可以通过具有不同的结构来完成。函数 doSomething 正在改变从外部给定的属性。不信任代码,您真的不知道您拥有什么以及将拥有什么。所有这些问题的发生是因为:可变结构表示指向内存的指针。
不变性与价值观有关
不变性意味着改变不是在同一个对象、结构上进行的,而是在新的对象、结构上进行的。这是因为引用不仅代表内存指针,还代表值。每一次改变都会创造新的价值,而不会触及旧的价值。如此清晰的规则回馈了信任和代码的可预测性。函数可以安全使用,因为它们不会改变,而是使用自己的值处理自己的版本。
使用值而不是内存容器可以确保每个对象都代表特定的不可更改的值,并且可以安全地使用它。
不可变结构表示值。
我在媒体文章中更深入地探讨了这个主题 - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310
曾几何时,线程之间的数据同步出现了问题。这个问题很痛苦,有10多个解决方案。有些人试图从根本上解决它。这是函数式编程诞生的地方。就像马克思主义一样。我无法理解 Dan Abramov 是如何将这个想法卖给 JS 的,因为它是单线程的。他是个天才。
我可以举个小例子。 gcc中有一个属性__attribute__((pure))
。如果您不特别声明它,编译器会尝试解决您的函数是否纯。即使您的状态是可变的,您的功能也可以是纯的。不可变性只是 100 多种保证您的函数是纯净的方法之一。实际上,您 95% 的函数都是纯函数。
如果你真的没有一个严肃的理由,你不应该使用任何限制(比如不变性)。如果你想 "Undo" 一些状态,你可以创建交易。如果你想简化通信,你可以发送带有不可变数据的事件。由你决定。
我正在从 post 马克思主义共和国写这封邮件。我确信任何想法的激进化都是错误的。
不同的看法...
我的另一个回答从很实际的角度解决了这个问题,我仍然喜欢它。我决定将其添加为另一个答案而不是那个答案的附录,因为这是一个无聊的哲学咆哮,希望它也能回答这个问题,但并不真正符合我现有的答案。
TL;DR
即使在小型项目中,不变性也很有用,但不要因为它存在就认为它适合你。
很多,更长的回答
注意: 出于此答案的目的,我使用 'discipline' 这个词来表示为了某些利益而自我否定。
这与另一个问题的形式相似:"Should I use Typescript? Why are types so important in JavaScript?"。它也有类似的答案。考虑以下情况:
You are the sole author and maintainer of a JavaScript/CSS/HTML codebase of some 5000 lines. Your semi-technical boss reads something about Typescript-as-the-new-hotness and suggests that we may want to move to it but leaves the decision to you. So you read about it, play with it, etc.
所以现在你可以做出选择,你会转向 Typescript 吗?
Typescript 有一些引人注目的优势:智能感知、及早发现错误、预先指定您的 API、在重构破坏它们时轻松修复它们、更少的测试。 Typescript 也有一些成本:某些非常自然和正确的 JavaScript 习语在它不是特别强大的类型系统中建模可能很棘手,注释增加了 LoC,重写现有代码库的时间和精力,构建中的额外步骤管道等。更根本的是,它划分出一个 子集 可能正确的 JavaScript 程序,以换取你的代码 更有可能 [=61] 的承诺=] 是正确的。这是任意限制的。这就是重点:你施加了一些限制你的纪律(希望不要搬起石头砸自己的脚)。
回到问题,在上一段的上下文中改写:值得吗?
在所描述的场景中,我认为如果您非常熟悉中小型 JS 代码库,那么选择使用 Typescript 更美观而不是实用。这就是 很好,美学上没有 错误,只是它们不一定引人注目。
场景 B:
You change jobs and are now a line-of-business programmer at Foo Corp. You're working with a team of 10 on a 90000 LoC (and counting) JavaScript/HTML/CSS codebase with a fairly complicated build pipeline involving babel, webpack, a suite of polyfills, react with various plugins, a state management system, ~20 third-party libraries, ~10 internal libraries, editor plugins like a linter with rules for in-house style guide, etc. etc.
回到你 5k LoC guy/girl 的时候,这并不重要。甚至文档也不是 的大问题,即使在 6 个月后回到代码的特定部分,您也可以很容易地弄清楚。但现在纪律不仅是好的,而且 是必要的。该学科可能不涉及 Typescript,但 将 可能涉及某种形式的静态分析以及所有其他形式的编码学科(文档、风格指南、构建脚本、回归测试、CI).纪律不再是奢侈品,而是必需品。
所有这些都适用于 1978 年的 GOTO
:你的 C 语言小二十一点游戏可以使用 GOTO
s 和意大利面条逻辑,而且选择它并不是什么大不了的 - 你的-自己的冒险经历,但随着项目变得更大、更雄心勃勃,嗯,散漫 GOTO
的使用无法持续。所有这些都适用于今天的不变性。
就像静态类型一样,如果您没有与工程师团队一起处理大型代码库 maintaining/extending,选择使用不变性比实际更美观:它的好处仍然存在,但可能不会还远远超过成本。
但与所有有用的学科一样,有一点它不再是可选的。如果我想保持健康的体重,那么使用冰淇淋的纪律可能是可选的。但如果我想成为一名竞技运动员,我对是否吃冰淇淋的选择包含在我对目标的选择中。如果你想用软件改变世界,不变性可能是你需要避免它在自身重量下崩溃的一部分。
我为可变(或不可变)状态创建了一个与框架无关的开源 (MIT) 库,它可以替换所有那些不可变存储,如库(redux、vuex 等...)。
不可变状态对我来说很难看,因为有太多工作要做(很多简单的操作 read/write 操作),代码可读性差,大数据集的性能不可接受(整个组件重新-渲染:/)。
使用 deep-state-observer 我只能用点符号更新一个节点并使用通配符。我还可以创建状态历史记录(undo/redo/time 旅行)只保留那些已更改的具体值 {path:value}
= 更少的内存使用。
有了 deep-state-observer,我可以微调一些东西,而且我可以对组件行为进行精细控制,因此可以显着提高性能。代码更具可读性,重构也更容易 - 只需搜索和替换路径字符串(无需更改 code/logic)。
举个例子:
const userMessage = {
user: "userId",
topic: "topicId"
content: {}
}
validateMessage(userMessage)
saveMessage(userMessage)
sendMessageViaEmail(userMessage)
**sendMessageViaMobilePush(userMessage)**
console.log(userMessage) // => ?
现在回答一些问题:
- 在 mutable 代码中 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).
在immutable代码的同一行userMessage下面是什么?
const userMessage = {
user: "userId",
topic: "topicId",
content: {}
}
Easy right ?
你能猜猜在中的mutable代码中“id”是通过哪种方法更新的吗片段 1 ??
By sendMessageViaEmail.
Why?
Why not?
Well it was at first updated by saveMessage,
but then overridden by sendMessageViaEmail.
在 mutable 代码中人们没有收到推送消息 (sendMessageViaMobilePush)。你能猜出为什么吗??
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.
中使用可变对象的主要问题是什么
请注意,当复杂性增加时,很难检查设置的内容和位置,尤其是当您与其他人一起工作时。
不变性的主要优点是简单。
替换对象比修改现有对象更简单。
它可以让你专注于一个地方的正确性。而不是你的对象可能改变的每个可能的地方。
如果您的对象处于无效状态,则更容易修复,因为错误一定是在您创建它时发生的(因为它是不可变的)
我目前正在研究 React JS and React Native frameworks. On the half way road I came across Immutability or the Immutable-JS library,当时我正在阅读有关 Facebook 的 Flux 和 Redux 实现的文章。
问题是,为什么不变性如此重要?变异 objects 有什么问题?这不是让事情变得简单吗?
举个例子,让我们考虑一个简单的 新闻 reader 应用程序,其打开屏幕是新闻标题的列表视图。
如果我设置说一个 array of objects with a value initially 我无法操纵它。这就是不变性原则所说的,对吧? (如果我错了,请纠正我。) 但是,如果我有一个必须更新的新新闻 object 怎么办?通常情况下,我可以将 object 添加到数组中。 在这种情况下我该如何实现?删除商店并重新创建它? 向数组添加 object 不是更便宜的操作吗?
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
实际上,事实恰恰相反:可变性使事情变得更加复杂,至少在长期 运行 中是这样。是的,它使您的初始编码更容易,因为您可以随心所欲地更改内容,但是当您的程序变得更大时,它就会成为一个问题 – 如果一个值发生了变化,是什么改变了它?
当你让一切都变得不可变时,这意味着数据不能再被突然改变。你肯定知道,如果你将一个值传递给一个函数,它就不能在那个函数中被改变。
简而言之:如果您使用不可变值,就可以很容易地推断出您的代码:每个人都会得到一份您数据的唯一*副本,因此它不会乱用它并破坏您代码的其他部分.想象一下,这会使在多线程环境中工作变得多么容易!
注 1:根据您的操作,不变性可能会产生性能成本,但 Immutable.js 之类的东西会尽可能优化。
注 2:在不太可能的情况下,您不确定,Immutable.js 和 ES6 const
的意思完全不同。
In usual case, I could have just added the object to the array. How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation? PS: If the example is not the right way to explain immutability, please do let me know what's the right practical example.
是的,你的新闻例子非常好,你的推理是完全正确的:你不能只修改现有列表,所以你需要创建一个新列表:
var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
我最近一直在研究同一主题。我会尽力回答您的问题,并尝试分享我目前学到的知识。
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
基本上归结为不变性增加了可预测性、性能(间接)并允许进行突变跟踪。
可预测性
突变隐藏了变化,这会产生(意想不到的)副作用,这可能会导致严重的错误。当您强制执行不变性时,您可以使您的应用程序架构和心智模型保持简单,从而更容易推理您的应用程序。
性能
尽管向不可变对象添加值意味着需要创建一个新实例,其中需要复制现有值,并且需要将新值添加到新对象中,这会消耗内存,但不可变对象可以利用结构共享以减少内存开销。
All updates return new values, but internally structures are shared to drastically reduce memory usage (and GC thrashing). This means that if you append to a vector with 1000 elements, it does not actually create a new vector 1001-elements long. Most likely, internally only a few small objects are allocated.
您可以阅读更多相关内容 here。
变异追踪
除了减少内存使用外,不变性还允许您通过使用引用和值相等来优化您的应用程序。这使得查看是否有任何更改变得非常容易。例如,反应组件中的状态变化。您可以使用 shouldComponentUpdate
通过比较状态对象来检查状态是否相同,并防止不必要的渲染。
您可以阅读更多相关信息 here.
其他资源:
If I set say an array of objects with a value initially. I can't manipulate it. That's what immutability principle says, right?(Correct me if I am wrong). But, what if I have a new News object that has to be updated? In usual case, I could have just added the object to the array. How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation?
是的,这是正确的。如果您对如何在您的应用程序中实现这一点感到困惑,我建议您查看 redux 如何做到这一点以熟悉核心概念,它对我帮助很大。
我喜欢以 Redux 为例,因为它支持不变性。它有一个单一的不可变状态树(称为 store
),其中所有状态更改都是通过调度操作显式进行的,这些操作由接受先前状态以及所述操作(一次一个)和 returns 应用程序的下一个状态。您可以阅读更多关于它的核心原则 here.
redux的作者egghead.io where Dan Abramov上有一个很好的redux课程,对这些原理的解释如下(我稍微修改了代码以更好地适应场景):
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();
此外,这些视频更详细地演示了如何实现不变性:
虽然其他答案很好,但为了解决您关于实际用例的问题(来自对其他答案的评论),让我们跳出您的 运行 代码一分钟,看看无处不在的答案吧在你眼皮子底下:git。如果每次推送提交时都覆盖 存储库中的数据会怎样?
现在我们要解决不可变集合面临的问题之一:内存膨胀。 Git 足够聪明,不会在您每次进行更改时简单地制作新的文件副本,它只是跟踪差异。
虽然我不太了解 git 的内部工作原理,但我只能假设它使用与您引用的库类似的策略:结构共享。在幕后,库使用 tries 或其他树来仅跟踪不同的节点。
此策略对于内存数据结构也具有合理的性能,因为 well-known 树运算算法以对数时间运行。
另一个用例:假设你想在你的网络应用程序上有一个撤销按钮。使用不可变的数据表示,实现这些相对微不足道。但是如果你依赖于变异,那就意味着你必须担心缓存世界状态和进行原子更新。
简而言之,运行时性能和学习曲线的不变性是要付出代价的。但是任何有经验的程序员都会告诉您,调试时间比编写代码的时间要多出一个数量级。对运行时性能的轻微影响可能被您的用户不必忍受的与状态相关的错误所抵消。
Why is immutability so important(or needed) in JavaScript?
可以在不同的上下文中跟踪不变性,但最重要的是根据应用程序状态和应用程序跟踪它 UI。
我会认为 JavaScript Redux 模式是非常时尚和现代的方法,因为你提到了这一点。
对于UI,我们需要使其可预测。
如果UI = f(application state)
.
应用程序(在 JavaScript 中)确实通过使用 reducer 函数.
实现的操作来更改状态reducer 函数简单地接受动作和旧状态,returns 新状态,保持旧状态不变。
new state = r(current state, action)
好处是:您 time-travel 状态,因为所有状态对象都已保存,并且您可以在任何状态下呈现应用程序,因为 UI = f(state)
所以你可以undo/redo轻松。
碰巧创建所有这些状态仍然可以节省内存,与 Git 的类比很好,我们在 Linux OS 中有类似的类比符号链接(基于索引节点)。
不变性的逆向观点
TL/DR:不变性在 JavaScript 中更像是一种时尚趋势,而不是必需品。如果您使用的是 React,它确实为某些 confusing design choices in state management. However in most other situations it wont add enough value over the complexity it introduces, serving more to pad up a resume 提供了一种巧妙的解决方法,而不是满足实际客户的需求。
长答案:阅读下文。
Why is immutability so important(or needed) in javascript?
好吧,很高兴你提出这个问题!
前段时间,一个非常有才华的人 Dan Abramov wrote a javascript state management library called Redux which uses pure functions and immutability. He also made some really cool videos 让这个想法很容易理解(和销售)。
时机非常完美。 Angular was fading, and JavaScript world was ready to fixate on the latest thing that had the right degree of cool, and this library was not only innovative but slotted in perfectly with React which was being peddled by another Silicon Valley powerhouse.
的新意时尚在 Java 脚本的世界中占据统治地位,这可能令人难过。现在阿布拉莫夫被誉为半神,我们所有凡人都必须服从 Dao of Immutability... 无论是否有意义。
What is wrong in mutating objects?
没有!
事实上,程序员一直在改变对象……只要有对象要改变。 50+ years 换句话说,应用程序开发。
为什么要把事情复杂化?当你有对象 cat
并且它死了,你真的需要第二个 cat
来跟踪变化吗?大多数人只会说 cat.isDead = true
并完成它。
Doesn't (mutating objects) make things simple?
是的! .. 当然可以!
特别是在 JavaScript 中,它在实践中最有用的是用于呈现在其他地方(例如在数据库中)维护的某些状态的视图。
What if I have a new News object that has to be updated? ... How do I achieve in this case? Delete the store & recreate it? Isn't adding an object to the array a less expensive operation?
好吧,您可以采用传统方法并更新 News
对象,这样您在内存中对该对象的表示就会发生变化(以及向用户显示的视图,或者人们希望如此)。 .
或者……
您可以尝试性感的 FP/Immutability 方法并将您对 News
对象的更改添加到 跟踪每个历史更改的数组中 这样您就可以迭代通过数组并找出正确的状态表示应该是什么(呸!)。
I am trying to learn what's right here. Please do enlighten me :)
时尚来来去去,伙计。给猫剥皮的方法有很多种。
很抱歉,您不得不忍受一套不断变化的编程范式带来的困惑。但是,嘿,欢迎加入俱乐部!!
关于不可变性,现在要记住几个要点,你会以只有天真才能鼓起的狂热强度向你抛出这些。
1) 不变性对于避免 race conditions in multi-threaded environments.
非常有用多线程环境(如 C++、Java 和 C#)在多个线程想要更改对象时会锁定对象。这对性能不利,但比数据损坏的替代方案要好。还不如让一切都变得不可变(赞美主Haskell!)。
但是,唉!在 JavaScript 中你总是 operate on a single thread. Even web workers (each runs inside a separate context)。因此,由于您的执行上下文中不能有 线程相关的 竞争条件(所有那些可爱的全局变量和闭包),所以支持不可变性的要点是 window.
(话虽如此, 在 Web Worker 中使用纯函数有一个优势,那就是您不会期望在主线程上摆弄对象。 )
2) 不变性可以(以某种方式)避免应用状态中的竞争条件。
这才是问题的真正症结所在,大多数 (React) 开发人员会告诉您,不可变性和 FP 可以以某种方式发挥这种魔力,让您的应用程序的状态变得可预测。
当然,这并不意味着您可以避免 race conditions in the database, to pull that one off you’d have to coordinate all users in all browsers, and for that you’d need a back-end push technology like WebSockets(更多内容见下文),它将向 运行 应用程序的每个人广播更改。
也不意味着 JavaScript 中存在一些固有问题,您的应用程序状态需要不变性才能变得可预测,任何在 React 之前编写前端应用程序的开发人员都会告诉您这一点.
这个相当令人困惑的说法只是意味着 如果你使用 React,你的应用程序很容易出现竞争条件,但不可变性可以让你消除这种痛苦。为什么?因为 React 很特别.. 它首先被设计为 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 的关系很大:如果你的 new 中有一堆相互依赖的变化应用程序并没有简单的方法来弄清楚你当前的状态,you are going to get confused,因此 使用不可变性来跟踪每个历史变化是非常有意义的。
3) 比赛条件绝对糟糕。
嗯,如果您使用的是 React,它们可能是。但如果你选择不同的框架,它们就很少见了。
此外,您通常有更大的问题需要处理……诸如依赖地狱之类的问题。就像一个臃肿的代码库。就像您的 CSS 没有加载一样。就像一个缓慢的构建过程或被困在一个单一的后端,这使得迭代几乎不可能。就像没有经验的开发者不理解发生了什么,把事情搞得一团糟。
你知道的。现实。但是,嘿,谁在乎这个?
4) 不变性利用 Reference Types 来减少跟踪每个状态变化对性能的影响。
因为严重的是,如果每次状态发生变化时你都要复制东西,你最好确保你对此很聪明。
5) 不变性允许你撤消内容。
因为呃..这是您的项目经理要求的第一个功能,对吧?
6) 不可变状态与 WebSockets 结合有很多很酷的潜力
最后但同样重要的是,状态增量的累积与 WebSockets 相结合是一个非常引人注目的案例,它允许轻松使用 state as a flow of immutable events...
一旦在这个概念上投入一分钱(状态是事件流 -- 而不是代表最新观点的粗略记录集),不变的世界就变成了一个神奇的世界居住的地方。一片土地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的当下,将自己的改变写回公共流程。
但在某个时候你醒来,意识到所有的奇迹和魔法 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。
现在给点建议,你是不是应该选择接受。
选择使用 FP/Immutability 编写 Java 脚本也是使您的应用程序代码库更大、更复杂且更难管理的选择。我强烈主张将这种方法限制在你的 Redux 减速器上,除非你知道你在做什么......如果你要继续使用不可变性,那么应用 immutable state to your whole application stack,而不仅仅是客户端-一方面,否则你会错过它的真正价值。
现在,如果您有幸能够在工作中做出选择,那么请尝试使用您的智慧(或不智慧)和 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 方法。他们可能更适合你。
简而言之,关于不变性,要说的是它 将 让你在同龄人中变得时尚,至少在下一次热潮到来之前,到那时你'会很高兴继续前进。
现在,在本次自我治疗之后,我想指出,我已将此作为文章添加到我的博客中 => Immutability in JavaScript: A Contrarian View。如果您有强烈的感觉也想发泄一下,请随时在那里回复 ;)。
我认为支持不可变对象的主要原因是保持对象的状态有效。
假设我们有一个名为 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 始终处于有效状态。
不变性在 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
要弄清楚,您需要仔细检查 class 内部结构,因为检查 public 接口并不能立即看出这一点。在具有大量可变状态和 classes.
的大型代码库中,此问题可能会爆发如果 Foo
是不可变的,那么这就不再是问题了。可以安全地假设我们可以按任何顺序调用 baz
或 bar
,因为 class 的内部状态无法更改。
The question is, why is immutability so important? What is wrong in mutating objects? Doesn't it make things simple?
关于可变性
从技术角度来看,可变性没有错。它很快,它正在重新使用内存。开发人员从一开始就习惯了(我记得)。可变性的使用存在问题,这种使用可能带来麻烦。
如果对象不与任何东西共享,例如存在于函数范围内并且不暴露给外部,那么很难看到不变性的好处。实际上,在这种情况下,不可变是没有意义的。当某些东西被共享时,不变性的感觉就开始了。
变异性头痛
可变共享结构很容易造成许多陷阱。访问引用的代码的任何部分的任何更改都会影响具有此引用可见性的其他部分。这种影响将所有部分连接在一起,即使它们不应该意识到不同的模块。一个函数中的突变可能会导致应用程序的不同部分崩溃。这种东西是不好的副作用。
下一个经常发生的突变问题是损坏的状态。当变异过程在中间失败时,可能会发生损坏的状态,一些字段被修改而一些字段没有被修改。
更重要的是,随着变异很难追踪变化。简单的参考检查不会显示差异,要了解更改的内容需要进行一些深度检查。此外,为了监控变化,需要引入一些可观察的模式。
最后,突变是信任赤字的原因。如果可以变异,您如何确定某些结构具有所需的价值。
const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }
如上例所示,传递可变结构总是可以通过具有不同的结构来完成。函数 doSomething 正在改变从外部给定的属性。不信任代码,您真的不知道您拥有什么以及将拥有什么。所有这些问题的发生是因为:可变结构表示指向内存的指针。
不变性与价值观有关
不变性意味着改变不是在同一个对象、结构上进行的,而是在新的对象、结构上进行的。这是因为引用不仅代表内存指针,还代表值。每一次改变都会创造新的价值,而不会触及旧的价值。如此清晰的规则回馈了信任和代码的可预测性。函数可以安全使用,因为它们不会改变,而是使用自己的值处理自己的版本。
使用值而不是内存容器可以确保每个对象都代表特定的不可更改的值,并且可以安全地使用它。
不可变结构表示值。
我在媒体文章中更深入地探讨了这个主题 - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310
曾几何时,线程之间的数据同步出现了问题。这个问题很痛苦,有10多个解决方案。有些人试图从根本上解决它。这是函数式编程诞生的地方。就像马克思主义一样。我无法理解 Dan Abramov 是如何将这个想法卖给 JS 的,因为它是单线程的。他是个天才。
我可以举个小例子。 gcc中有一个属性__attribute__((pure))
。如果您不特别声明它,编译器会尝试解决您的函数是否纯。即使您的状态是可变的,您的功能也可以是纯的。不可变性只是 100 多种保证您的函数是纯净的方法之一。实际上,您 95% 的函数都是纯函数。
如果你真的没有一个严肃的理由,你不应该使用任何限制(比如不变性)。如果你想 "Undo" 一些状态,你可以创建交易。如果你想简化通信,你可以发送带有不可变数据的事件。由你决定。
我正在从 post 马克思主义共和国写这封邮件。我确信任何想法的激进化都是错误的。
不同的看法...
我的另一个回答从很实际的角度解决了这个问题,我仍然喜欢它。我决定将其添加为另一个答案而不是那个答案的附录,因为这是一个无聊的哲学咆哮,希望它也能回答这个问题,但并不真正符合我现有的答案。
TL;DR
即使在小型项目中,不变性也很有用,但不要因为它存在就认为它适合你。
很多,更长的回答
注意: 出于此答案的目的,我使用 'discipline' 这个词来表示为了某些利益而自我否定。
这与另一个问题的形式相似:"Should I use Typescript? Why are types so important in JavaScript?"。它也有类似的答案。考虑以下情况:
You are the sole author and maintainer of a JavaScript/CSS/HTML codebase of some 5000 lines. Your semi-technical boss reads something about Typescript-as-the-new-hotness and suggests that we may want to move to it but leaves the decision to you. So you read about it, play with it, etc.
所以现在你可以做出选择,你会转向 Typescript 吗?
Typescript 有一些引人注目的优势:智能感知、及早发现错误、预先指定您的 API、在重构破坏它们时轻松修复它们、更少的测试。 Typescript 也有一些成本:某些非常自然和正确的 JavaScript 习语在它不是特别强大的类型系统中建模可能很棘手,注释增加了 LoC,重写现有代码库的时间和精力,构建中的额外步骤管道等。更根本的是,它划分出一个 子集 可能正确的 JavaScript 程序,以换取你的代码 更有可能 [=61] 的承诺=] 是正确的。这是任意限制的。这就是重点:你施加了一些限制你的纪律(希望不要搬起石头砸自己的脚)。
回到问题,在上一段的上下文中改写:值得吗?
在所描述的场景中,我认为如果您非常熟悉中小型 JS 代码库,那么选择使用 Typescript 更美观而不是实用。这就是 很好,美学上没有 错误,只是它们不一定引人注目。
场景 B:
You change jobs and are now a line-of-business programmer at Foo Corp. You're working with a team of 10 on a 90000 LoC (and counting) JavaScript/HTML/CSS codebase with a fairly complicated build pipeline involving babel, webpack, a suite of polyfills, react with various plugins, a state management system, ~20 third-party libraries, ~10 internal libraries, editor plugins like a linter with rules for in-house style guide, etc. etc.
回到你 5k LoC guy/girl 的时候,这并不重要。甚至文档也不是 的大问题,即使在 6 个月后回到代码的特定部分,您也可以很容易地弄清楚。但现在纪律不仅是好的,而且 是必要的。该学科可能不涉及 Typescript,但 将 可能涉及某种形式的静态分析以及所有其他形式的编码学科(文档、风格指南、构建脚本、回归测试、CI).纪律不再是奢侈品,而是必需品。
所有这些都适用于 1978 年的 GOTO
:你的 C 语言小二十一点游戏可以使用 GOTO
s 和意大利面条逻辑,而且选择它并不是什么大不了的 - 你的-自己的冒险经历,但随着项目变得更大、更雄心勃勃,嗯,散漫 GOTO
的使用无法持续。所有这些都适用于今天的不变性。
就像静态类型一样,如果您没有与工程师团队一起处理大型代码库 maintaining/extending,选择使用不变性比实际更美观:它的好处仍然存在,但可能不会还远远超过成本。
但与所有有用的学科一样,有一点它不再是可选的。如果我想保持健康的体重,那么使用冰淇淋的纪律可能是可选的。但如果我想成为一名竞技运动员,我对是否吃冰淇淋的选择包含在我对目标的选择中。如果你想用软件改变世界,不变性可能是你需要避免它在自身重量下崩溃的一部分。
我为可变(或不可变)状态创建了一个与框架无关的开源 (MIT) 库,它可以替换所有那些不可变存储,如库(redux、vuex 等...)。
不可变状态对我来说很难看,因为有太多工作要做(很多简单的操作 read/write 操作),代码可读性差,大数据集的性能不可接受(整个组件重新-渲染:/)。
使用 deep-state-observer 我只能用点符号更新一个节点并使用通配符。我还可以创建状态历史记录(undo/redo/time 旅行)只保留那些已更改的具体值 {path:value}
= 更少的内存使用。
有了 deep-state-observer,我可以微调一些东西,而且我可以对组件行为进行精细控制,因此可以显着提高性能。代码更具可读性,重构也更容易 - 只需搜索和替换路径字符串(无需更改 code/logic)。
举个例子:
const userMessage = {
user: "userId",
topic: "topicId"
content: {}
}
validateMessage(userMessage)
saveMessage(userMessage)
sendMessageViaEmail(userMessage)
**sendMessageViaMobilePush(userMessage)**
console.log(userMessage) // => ?
现在回答一些问题:
- 在 mutable 代码中 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).
在immutable代码的同一行userMessage下面是什么?
const userMessage = { user: "userId", topic: "topicId", content: {} } Easy right ?
你能猜猜在中的mutable代码中“id”是通过哪种方法更新的吗片段 1 ??
By sendMessageViaEmail. Why? Why not? Well it was at first updated by saveMessage, but then overridden by sendMessageViaEmail.
在 mutable 代码中人们没有收到推送消息 (sendMessageViaMobilePush)。你能猜出为什么吗??
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.
中使用可变对象的主要问题是什么请注意,当复杂性增加时,很难检查设置的内容和位置,尤其是当您与其他人一起工作时。
不变性的主要优点是简单。
替换对象比修改现有对象更简单。
它可以让你专注于一个地方的正确性。而不是你的对象可能改变的每个可能的地方。
如果您的对象处于无效状态,则更容易修复,因为错误一定是在您创建它时发生的(因为它是不可变的)