Array.Reduce() — 用前三思!
你经常使用 Array.reduce() 么?
Array.reduce()
在 JavaScript 开发者中是一个比较流行的数组方法。并在 JavaScript 生态中被广泛使用和讨论。
令人惊奇的是,我们在很多情况下不使用 Array.reduce()
会更好。在本文中,让我们一起看下其中的一些情况,并讨论下更好的实现方式。
不好的 Reduce() 使用方法
不过,当情况超出 reduce 方法的初始目的时,问题也会随之出现。
一起看下如下的 reduce() 使用示例
示例 01 - 看看你能多快的理解它?
const graph =
edges.reduce((g, edge) => {
const [u, v] = edge;
if (!g.has(u) g.set(u, []);
if (!g.has(v) g.set(v, []);
g.get(u).push(v);
return g;
}, new Map());
理解 - 庖丁解牛
现在让我们后退一步,看下上述函数到底做了什么。
步骤 01 - 从末尾开始
首先,你需要看到末尾才知道这个函数从那里开始:new Map()
。然后只有编写代码的你才能意识到空映射对象是所有逻辑的开始。
步骤 02
随后我们就能理解,上述映射对象将被 reduce
函数返回。
步骤 03
最后,尝试去理解这个函数的核心部分,我们从 edges
数组提取出两个节点(或者说端点),并且添加到我们自己的图形对象中。
你能快速理解上述函数的用意是从边数组中提取单个数组并构建一个图形么?是否还是难以消化?
为何会如此难懂?
我想现在大家都会同意,为了理解上述代码从而来回跳转有点繁琐了吧。
通常,在给方法传递一个函数时,最习惯的模式是 回调模式。
其中第一个参数是 数据,随后才是 回调函数。
method(data , callbackFunction)
不过 reduce() 函数并不遵循此模式。因此开发者们通常会难以理解。
广告:在不同项目间使用 Bit (Github)分享可复用的组件。Bit 使任何项目中的独立组件都能方便的共享、记录、依赖组织。
使用它可以最大限度的实现代码重用、独立组件协作并构建可扩展的应用程序。
Bit 支持 Node,TypeScript, React, Vue, Angular 以及其他更多.
如何重写? - 令其更具可读性
让我们看下我们如何在不使用 reduce()
方法的情况下,以更易读的方式编写上述代码。
const g = new Map();
for (const [u, v] of edges) {
if (!g.has(u)) g.set(u, []);
if (!g.has(v)) g.set(v, []);
g.get(u).push(v);
}
当我们从上往下阅读代码时,我们可以理解这段代码的含义是为图形创建一个空的 Map
,然后在循环中使用 edges 数组去构建它。
现在的代码比之前的 reduce() 版本更容易理解了吧?
继续看下其他几个示例:
示例02 - 数组展开
在如下的示例中,使用 Array.reduce() 来展开数组。
const arr = [[23,45,56], [1,2,3,4], [11,46,77]];
const flatArr = arr.reduce((final, elem) => {
if (Array.isArray(elem))
return [...final, ...elem];
return [...final, elem];
}, []);
我们展开数组 arr
并赋值给一个名为 flatArr
的新数组。跟上边例子一样,这段代码可读性不是很好。
重构一下 - 可读性++
我们可通过 for
循环或 for of
循环来优化它。不过在 es6
中,更好的方法是使用 Array.flat()
方法。
const arr = [[23,45,56], [1,2,3,4], [11,46,77]];
const flatArr = arr.flat();
Array.flat()
方法明显更具可读性。如果没有 flat()
方法可用,更好的实现方法是:
const flatArr = [];
for(const el of arr) {
if(Array.isArray(elem)
flatArr.push(...elem);
else
flatArr.push(elem);
}
再次说明,从上往下阅读上述代码时,我们能更容易理解其含义。
示例 03 - 把数组转换为对象
在接下来的示例中,使用 reduce() 方法将一组键值对转换为对象。
const keyValuePairs = [
['name', 'dilantha'],
['country', 'Sri Lanka']
];
const userObject = keyValuePairs.reduce((userObj, value) => {
userObj[value[0]] = value[1];
return userObj;
} , {});
将结果保存到 userObject
变量中,如下是它的结果:
{name: 'dilantha', country: 'Sri Lanka'}
让我们把 Reduce() 重构到一行
我们可以使用 Object.fromEntries()
来简化这一过程。Object.fromEntries()
将会接收一个键值对列表,并将其转换为如上所示的对象。
const userObject = Object.fromEntries(keyValuePairs);
示例04 - 奇数统计
如下示例将展示如何使用 reduce() 函数去统计 nodes 列表中的奇数值数量。
const nodes = [
{ id: '1', value: 1 },
{ id: '2', value: 2 },
{ id: '3', value: 3 },
null,
{ id: '4', value: 4 },
];
const oddNodeCount = nodes.filter(Boolean).reduce((count, node) => {
return node.value % 1 === 0 ? count : count + 1;
}, 0);
再次说明,我们需要阅读到函数末尾才能深入的理解这个函数。有没有更好的方式来实现这个函数呢?
使用 Map() 和 Filter() 取代 Reduce()
我们可使用 map() 和 filter() 函数来取代 replace() 函数。请看如下的代码片段:
const nodes = [
{ id: '1', value: 1 },
{ id: '2', value: 2 },
{ id: '3', value: 3 },
null,
{ id: '4', value: 4 },
];
const oddNodeCount = nodes.map(node => node && node.value % 2 === 1)
.filter(Boolean).length;
你认为这样理解起来比 reduce() 更简单了么?
结论
正如上面实例所述,可以看到有很多实例可以使用相比 reduce() 更好的方式去实现以提升函数可读性。
现在的问题是,您是否应该倾向于使用不同的实现方式取代使用 reduce()
?嗯,大多数情况下答案是“肯定”的。毕竟,可读性确实很重要,而 reduce()
是以牺牲可读性为代价的。
感谢您阅读至此,我确信你对此话题也会有很多疑惑和思考。不要忘记下方留言评论哟。
此文灵感源于谷歌 Chrome 开发者所发布的 视频 ,所以别忘了看下。
编码愉快~
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
译者注:其实
reduce
对数据降维或统计信息还是很有用的,文中的示例4如果使用filter()
和map()
则会遍历两遍数组并创建一个新数组,时间和空间复杂度都是O(n)
,个人并不赞同