在所有后 ES6 时代的数组方法中,我觉得最难理解的就是
Array.reduce()
。
从表面上看,它似乎是一个简单无趣的方法,并没有太大作用。 但是在不起眼的外表之下,
Array.reduce()
实际上是对开发人员工具包的强大而灵活的补充。
今天,我们就来研究一下通过
Array.reduce()
可以完成的一些有意思的事情。
大部分现代的数组方法都返回一个新的数组,而
Array.reduce()
更加灵活。它可以返回任意值,它的功能就是将一个数组的内容聚合成单个值。
这个值可以是数字、字符串,甚至可以是对象或新数组。这就是一直难住我的部分,我没想到它这么灵活!
Array.reduce()
接受两个参数:一个是对数组每个元素执行的回调方法,一个是初始值。
这个回调接收4个参数,前两个参数是:
accumulator
是当前聚合值,
current
是数组循环时的当前元素。无论你返回什么值,都将作为累加器提供给循环中的下一个元素。初始值将作为第一次循环的累加器。
var myNewArray = [].reduce(
function
(accumulator, current) {
return
accumulator;
}, starting);
复制代码
让我们来看几个实际例子。
1. 数组求和
假设你想把一组数字加在一起。使用
Array.forEach()
大概可以这么做:
var total = 0;
[1, 2, 3].forEach(
function
(num) {
total += num;
复制代码
这是
Array.reduce()
用得最多的例子了。我发现* accumulator *这个单词让人困惑,所以在示例中我改为
sum
,因为这里就是求和的意思。
var total = [1, 2, 3].reduce(
function
(sum, current) {
return
sum + current;
}, 0);
复制代码
这里传入
0
作为初始值。
在回调里,将当前值加入到
sum
,第一轮循环时它的值是初始值
0
,然后变成
1
(初始值
0
加上当前元素值
1
),然后变成
3
(累加值
1
加上当前元素值
2
),以此类推
2. 组合多个数组方法
假设有一个
wizards
数组:
var wizards = [
name:
'Harry Potter'
,
house:
'Gryfindor'
name:
'Cedric Diggory'
,
house:
'Hufflepuff'
name:
'Tonks'
,
house:
'Hufflepuff'
name:
'Ronald Weasley'
,
house:
'Gryfindor'
name:
'Hermione Granger'
,
house:
'Gryfindor'
复制代码
你想创建一个仅包含住在 Hufflepuff 的巫师名字的新数组。一个可行的方法是使用
Array.filter()
方法获取
house
属性为
Hufflepuff
的
wizards
。然后用
Array.map()
方法创建一个只包含过滤后对象的
name
属性的新数组。
var hufflepuff = wizards.filter(
function
(wizard) {
return
wizard.house ===
'Hufflepuff'
;
}).map(
function
(wizard) {
return
wizard.name;
复制代码
使用
Array.reduce()
方法,我们可以用一步得到同样的结果,提高了性能。传递一个空数组
[]
作为初始值。每次循环时判断
wizard.house
是否为
Hufflepuff
。如果是,就加入到
newArr
中(即
accumulator
),否则啥也不做。
无论判断条件是否成立,最后都返回
newArr
作为下一次循环的
accumulator
。
var hufflepuff = wizards.reduce(
function
(newArr, wizard) {
if
(wizard.house ===
'Hufflepuff'
) {
newArr.push(wizard.name);
return
newArr;
}, []);
复制代码
3. 从数组生成 HTML 标签
那么,如果想创建一个由住在 Hufflepuff 的巫师组成的无序列表要怎么做呢?这次不是给
Array.reduce()
传一个空数组作为初始值了,而是一个名为
html
的空字符串
''
。
如果
wizard.house
等于
Hufflepuff
,我们就将
wizard.name
用列表项
li
包裹起来,再拼接到
html
字符串里。然后返回
html
作为下一次循环的
accumulator
。
var hufflepuffList = wizards.reduce(
function
(html, wizard) {
if
(wizard.house ===
'Hufflepuff'
) {
html +=
'<li>'
+ wizard.name +
'</li>'
;
return
html;
},
''
);
复制代码
在
Array.reduce()
前后添加无序列表的开始和结束标记,就可以把它插入到 DOM 中了。
var hufflepuffList =
'<ul>'
+ wizards.reduce(
function
(html, wizard) {
if
(wizard.house ===
'Hufflepuff'
) {
html +=
'<li>'
+ wizard.name +
'</li>'
;
return
html;
},
''
) +
'</ul>'
;
复制代码
4. 数组元素分组
lodash 有个
groupBy()
方法,可以将数组元素按照某个标准分组。
假设你有一个数字数组。
如果你想把
numbers
数组中的元素按照整数部分的值分组,用 lodash 可以这样做:
var numbers = [6.1, 4.2, 6.3];
// 返回 {
'4'
: [4.2],
'6'
: [6.1, 6.3]}
_.groupBy(numbers, Math.floor);
复制代码
如果你有一个单词数组,你想根据
words
中的单词长度分组,你可以这样做:
var words = [
'one'
,
'two'
,
'three'
];
// 返回 {
'3'
: [
'one'
,
'two'
],
'5'
: [
'three'
]}
_.groupBy(words,
'length'
);
复制代码
用
Array.reduce()
实现
groupBy()
函数
你可以用
Array.reduce()
方法实现同样的功能。
我们来创建一个工具函数
groupBy()
,接受数组和分组条件作为参数。在
groupBy()
内部,在数组上执行
Array.reduce()
,传一个空对象
{}
作为初始值,然后返回结果。
var groupBy =
function
(arr, criteria) {
return
arr.reduce(
function
(obj, item) {
// 省略代码
}, {});
复制代码
在
Array.reduce()
回调函数内部,我们会判断
criteria
是函数还是
item
的属性。然后获取当前
item
的值。
如果
obj
中还不存在这个属性,则创建它,并将一个空数组赋值给它。最后,将
item
添加到
key
的数组中,再返回该对象作为下一次循环的
accumulator
。
var groupBy =
function
(arr, criteria) {
return
arr.reduce(
function
(obj, item) {
// 判断criteria是函数还是属性名
var key = typeof criteria ===
'function'
? criteria(item) : item[criteria];
// 如果属性不存在,则创建一个
if
(!obj.hasOwnProperty(key)) {
obj[key] = [];
// 将元素加入数组
obj[key].push(item);
// 返回这个对象
return
obj;
}, {});
复制代码
5. 合并数据到单个数组
还记得前面的
wizards
数组吗?
var wizards = [
name:
'Harry Potter'
,
house:
'Gryfindor'
name:
'Cedric Diggory'
,
house:
'Hufflepuff'
name:
'Tonks'
,
house:
'Hufflepuff'
name:
'Ronald Weasley'
,
house:
'Gryfindor'
name:
'Hermione Granger'
,
house:
'Gryfindor'
复制代码
如果还有另一份数据,每个巫师获得的的积分对象:
var points = {
HarryPotter: 500,
CedricDiggory: 750,
RonaldWeasley: 100,
HermioneGranger: 1270
复制代码
假设你想把两份数据合并到一个数组,也就是把
points
数值添加到每个巫师对象上。你会怎么做?
Array.reduce()
方法特别适合!
var wizardsWithPoints = wizards.reduce(
function
(arr, wizard) {
// 移除巫师名字中的空格,用来获取对应的 points
var key = wizard.name.replace(
' '
,
''
);
// 如果wizard有points,则加上它,否则设置为0
if
(points[key]) {
wizard.points = points[key];
}
else
{
wizard.points = 0;
// 把wizard对象加入到新数组里
arr.push(wizard);
// 返回这个数组
return
arr;
}, []);
复制代码
其实这里用
Array.map
也很方便实现。
6. 合并数据到单个对象
如果你想合并两个来源的数据到一个对象中,也就是巫师的名字作为属性名,house 和 points 作为属性值,要怎么做呢?同样,
Array.reduce()
很合适。
var wizardsAsAnObject = wizards.reduce(
function
(obj, wizard) {
// 移除巫师名字中的空格,用来获取对应的 points
var key = wizard.name.replace(
' '
,
''
);
// 如果wizard有points,则加上它,否则设置为0
if
(points[key]) {
wizard.points = points[key];
}
else
{
wizard.points = 0;
// 删除 name 属性
delete wizard.name;
// 把 wizard 数据添加到新对象中
obj[key] = wizard;
// 返回该对象
return
obj;
}, {});
复制代码
总结:
Array.reduce()
真香
Array.reduce()
方法从我曾经认为不堪大用的东西,变成我最喜欢的 JavaScript 方法。那么,你应该使用它吗?什么时候可以用?
Array.reduce()
方法有着良好的浏览器支持。所有的现代浏览器都支持,包括 IE9 及以上。移动端浏览器也在很早之前就支持了。如果你还需要支持更老的浏览器,你可以添加一个 polyfill 来支持到
IE6
。
Array.reduce()
最大的槽点可能就是对于从来没接触过的人来说有点费解。组合使用
Array.filter()
和
Array.map()
执行起来更慢,并且包含多余的步骤,但是更容易阅读,从方法名可以明显看出它要做的事情。
尽管如此,有时候
Array.reduce()
也可以让复杂的事情看起来更简单。
groupBy()
工具函数就是个很好的例子。
最后,它应该成为你的工具箱里的另一个工具,一个使用得当就威力无穷的工具。
原文:
24ways.org/2019/five-i…
由1024译站翻译整理
有个小小的请求
在掘金已经写了20多篇技术文章了,承蒙大家厚爱,有过几篇阅读数过万的文章,总阅读数也突破了10万。在这里非常感谢各位的金手指!但是也有个小小的遗憾,每篇文章后面我都留了我的微信公众号1024译站二维码,但不知道为什么关注的人非常少。要是每篇读者有10%的人来关注,估我计做梦都会笑醒吧,哈哈……像极了小时候的梦想“要是全国每人给我一块钱,我就有好多个亿了!”如果发生了,只能说是个
奇迹
。现在我就有这个疯狂的想法,想邀请你来一起创造这个
奇迹
!只要动动你的金手指,关注下1024译站这个公众号,就能第一时间收到我用心创作的技术干货!不花一分钱!微信最近也推出了付费阅读的功能,说明优质内容收费是大趋势,以后免费的优质内容越来越少了!我也建了微信粉丝群,如果真的发生奇迹,必定倾情回馈!二维码就在下面,手机扫起来,一起见证奇迹!