为什么要关心不变性?
AWS AI 直播!
在讨论函数式编程时,我们不可避免地会涉及到函数纯性、无点风格、递归、不可变性等等。你或许在日常工作中不会用到函数式编程的所有方面,但如果你经常使用像 RxJS、Cycle 这样的 JavaScript 库,或者像 Flux(Redux、Vuex)这样的状态管理工具,那么我敢肯定,你接触到不可变对象的频率远高于其他任何函数式特性。事实上,不可变性对于响应式编程至关重要,甚至可以将其视为响应式编程的基础之一。我们这里不讨论 JavaScript 中的字符串和其他基本类型,因为它们的设计本身就是不可变的。
对于一个响应式库来说,它需要在程序执行过程中保持状态不变,为什么呢?否则如何检测状态的变化呢?可以这样理解:由于 JavaScript 对象是短暂的(非持久化的),一旦你修改了某个属性,它的值就会改变,但对象本身并没有改变。如果你比较修改前后的对象,它们仍然是同一个。显然,你知道为什么,因为修改属性并不会创建一个新的对象!为了理解这一点,我假设你已经知道,在 JavaScript 中,一个变量实际上保存的是指向存储对象属性(以键值对的形式)的内存块的引用。你可能会说,可以通过对数据进行递归比较来检测变化?但当你的状态不断变化时,这种方法效率并不高!不可变性意味着对对象进行浅拷贝,并在新拷贝上进行修改。把复制步骤看作是状态发生变化的信号,这种理解是没错的。现在,这是一种速度更快、性能更高的方法来判断状态是否发生了变化。这可能还会引发另一个疑问:你为什么认为复制状态比递归检查已改变的属性更高效?嗯,问得好。我会在本文末尾尝试解答这个问题,现在我想说的是,有一种叫做结构共享的技术使得这一切成为可能。
在 Codepen 上尝试
从本质上讲,不变性具有以下优点
1.通过变更跟踪实现响应式——我们之前已经讨论过这一点。使用不可变状态可以使机器和我们开发者都能快速轻松地识别变更。Redux、Vuex 等工具,甚至React和 Vue 本身的部分代码,都是基于此构建响应式功能的。一旦状态发生改变,无论是基于异步后台活动还是用户与 UI 的交互,引用相等性检查都会立即发出信号,表明可能到了重新渲染的合适时机。
2.可预测性和更佳的调试——可预测性通常与函数纯度密切相关。对于一个自身不产生任何副作用的函数,无论调用多少次,对于同一组输入,其输出始终相同。基于函数不能修改共享状态的限制,我们现在有了像 Vuex 和 Redux 这样的工具,它们允许你修改状态,但方式必须符合其标准。例如,你只能通过在 Vuex store 中被列为mutation 的函数来修改 store 。你还可以使用像Vue.set()和Vue.delete()这样的方法来不可变地记录你的更改。这使得调试更加容易,输出/错误也更加可预测。
3.版本控制——显而易见,如果能够保存状态,就可以随时查看旧状态。这和在 Git 中多次合并后仍然可以访问旧代码非常相似。Redux 实现了一个名为“动作回放”( Action Replay)的功能,可以在浏览器中并排显示状态变化和用户交互。你觉得这有用吗?当然!既酷炫又实用。现在你知道保存状态有多么重要了吧。
4.性能- 我之所以把这一点放在最后,是因为在讨论性能时我没有提到结构共享。你可能还在疑惑,为什么每次简单的更改都创建新对象比对对象进行深度相等性检查更能提升性能。在讨论不可变性时,我也用到了“浅拷贝”这个术语,这应该已经给你一些提示了。如果没有,也不用担心。虽然复制操作很简单,但重要的是要意识到,你复制的对象可能包含嵌套对象作为其属性值。我们对那些不需要更改的对象进行浅拷贝(只复制引用而不创建新对象),只对真正需要更改的嵌套对象进行深度克隆。这就是我们所说的两个对象之间的结构共享。你通过内部引用共享整个结构,只重新创建需要修改的节点。这可能需要一个例子才能让你理解。
在 Codepen 上尝试
你看,这其实并不难,但当对象拥有成千上万个属性时,一旦需要修改非常非常深层的嵌套对象,就会变得异常棘手。更糟糕的是,你还可能担心误改其他嵌套对象。为了避免处理大量对象时的麻烦,你可以选择使用像immutable.js或immer 这样的库。如果你想了解更多关于结构共享的知识,我强烈推荐Yehonathan 的这篇文章。如果你想深入了解函数式编程,可以阅读这篇文章,从我的角度理解递归。
原文发布于此处 -
https://mayankav.webflow.io/blog/why-immutability
文章来源:https://dev.to/mayankav/why-care-about-immutability-119g
