大家好,很高兴又见面了,我是"前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
前端进阶
今天给大家带来的是Immutable vs. seamless-immutable的可变性方案之争,话不多少,直接开始。
1.什么是不变性?
图片来自Priya Pedamkar的“Mutable vs Immutable Java”
不变性是 Clojure 或 Lisp 等函数式语言中最著名的概念,本质是无论如何都不能改变数据。 但这是否意味着一旦创建了数据就无法更改它? 不,其实不然!只是意味着,每次想要更改数据中的任何内容时,都必须创建完整的副本,然后将更改应用到副本中再返回,即将所有数据结构视为以只读模式工作,而不能直接在上面修改,从而避免一些可能的问题。 比如下面的例子:
const arr = [1, 2, 3];
//想将数组中的第二个元素更改为 4,
//但也希望数据不可变,该怎么办?
const changedArr = [...arr.slice(0, 1), 4, ...arr.slice(2)];
// 下面是对象
const obj = {
foo: 'bar',
};
const changedObj = {
...foo,
// 创建副本
bar: 'baz',
};
2.为什么不可变性如此重要?
将代码中的每个数据结构都设置为不可变,可以让代码完全按照开发者想要的方式运行。 在 JavaScript 世界中,主要使用对值的引用,而不是值本身。 这意味着,每个有权访问引用的代码都可以更改它引用的值:
const foo = {
bar: 'baz',
// bar属性为baz字符串
};
someMysteriousFunction(foo);
// 真的相信这个函数,它不会改变foo对象吗?
console.log(foo.bar);
// 能确定它会打印“baz”吗?不能!
在上面的示例中, someMysteriousFunction 可能会改变 foo 属性。 这意味着开发者不能相信这个函数,不能相信任何可以访问该对象引用的代码, 除非它是不可变的。
不变性在 Redux 的上下文中尤为重要
不变性在 Redux 的上下文中尤为重要,因为它使 Redux 能够正确且快速地工作。 Redux(和 react-redux)使用浅层相等比较来决定状态和组件的更新,因此Redux 要求 reducer 是没有副作用的纯函数。
这意味着你必须从 reducer 返回一个新状态,而不是改变当前状态。 这个约定使 Redux 变得快速、可靠,它甚至可以启用时间旅行调试等高级功能。
3.Immutable.js现有问题?
上文已经知道不可变性是一个有价值的概念,而 Immutable.js通过引入自己的List, Stack, Map, OrderedMap, Set, OrderedSet 和Record等自定义对象,同时内置了对象的方法,也确实做到了这一点。
const { Map } = require('immutable');
// Map
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b') + ' vs. ' + map2.get('b');
// 2 vs. 50
然而,Immutable.js因为本身的设计问题,依然有一些和开发者无法调和的矛盾。
- 大量样板文件:在处理对象、数组时不能使用标准语法,而是必须使用 API
- 大多数库与它不兼容:必须广泛使用 fromJS 和 toJS 工具方法
- 修改成本高:因为它的语法存在于整个代码库中
- 性能提升不明显:在绝大多数使用它的应用程序中很少真正有预期的性能提升
- 明显拖慢开发速度
图片来自Aleksandar Ginovski的《4 Reasons To Love ImmutableJS》
4.Immutable.js的可行替代方案
既然Immutable.js有这么多问题,那么开发者应该如何解决呢?即,如何在项目中能有效的使用不可变的特性。简单场场景可以使用Rest替代,在复杂场景强烈建议使用seamless-immutable。
4.1 对象Rest运算符
Rest符的工作方式类似于 Object.assign ,但更加简洁。 它在所有现代 JavaScript 运行时中都是开箱即用的,也可以使用 Babel 等工具将其转换为旧的 ES5 代码。
const reducer = (state, action) => ({
...state,
myChangingProp: action.payload,
});
它是一种非常简单和优雅的语法,而且不需要使用任何import,开箱即用!当有更复杂的对象树并且想要更改嵌套很深的属性时,它的问题就出现了:
const reducer = (state, action) => ({
...state,
nestedObj: {
// 嵌套复制的麻烦
...state.nestedObj,
anotherNestedObj: {
...state.nestedObj.anotherNestedObj,
finallyAPropToChange: action.payload,
},
},
});
4.2 seamless-immutable
seamless-immutable是向后兼容普通数组和对象的不可变 JS 数据结构。 可以在 for 循环中使用它们,或将它们传递给需要普通 JavaScript 数据结构的函数等。
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);
array[1] = "I'm going to mutate you!"
array[1] // "immutable"
array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "Can’t Touch This"
for (var index in array) { console.log(array[index]); }
// "totally"
// "immutable"
// { hammer: 'Can’t Touch This' }
JSON.stringify(array)
//'["totally","immutable",{"hammer":"Can’t Touch This"}]'
注意:seamless-immutable的向后兼容性要求 Object.defineProperty 和 Object.freeze 等 ECMAScript 5 功能存在并正常工作。看一个使用seamless-immutable操作数据的示例:
import Immutable from 'seamless-immutable';
const initialState = Immutable({});
const reducer = (state, action) =>
state.setIn(['nestedObj', 'anotherNestedObj', 'finallyAPropToChange'], action.payload);
在seamless-immutable中,状态state不再是普通的 JavaSript 对象,而是通过Immutable包装后的对象,同时还必须使用 API 来进行更改操作。
虽然必须使用 API 来“改变”数据,但可以使用标准的点或括号表示法来访问它,也是因为seamless-immutable向后兼容普通 JS 对象和数组。
5.Immutable.js vs. seamless-immutable
包体积大小&加载速度
从包体积来看,immutable压缩后体积达到了17.7k,而seamless-immutable只有2.7k,前者是后者的6.5倍。同时,在包的下载速度上,3G网络下immutable需要353ms,4G下也需要20ms,远大于seamless-immutable在3G下的54ms和4G网络下的3ms。
Github/NPM的数据
从Github的数据来看,immutable创建于10年前,seamless-immutable创建于8年前,前者的star数达到了32,454,而后者只有5,368。所以,从整体看来,seamless-immutable相对比较年轻,在知名度上也会稍微差一点。
同时,放开来看过去5年的数据,immutable处于急剧的爆发期,而seamless-immutable则相对平缓很多。
从周下载量来看immutable也远胜于seamless-immutable。
seamless-immutable vs. immutable周下载量
综合来看,如果对于项目有严格的性能要求,非常建议大家使用seamless-immutable而不是immutable。虽然seamless-immutable知名度稍低于immutable,但是不失为一个优秀的可变性解决方案。
6.本文总结
本文主要和大家介绍Immutable.js的可行替代方案seamless-immutable,文章从Github数据、NPM周下载量、使用趋势、性能等诸多维度进行了对比,相信大家对两者已经有一个比较深入的理解。文末的参考资料提供了大量优秀文档以供学习,如果有兴趣可以自行阅读。如果大家有什么疑问欢迎在评论区留言。
参考资料
https://leocode.com/development/you-may-not-need-immutable-js/
https://www.educba.com/mutable-vs-immutable-java/
https://www.npmjs.com/package/seamless-immutable
https://www.npmjs.com/package/immutable