Array 中有几个很实用的函数,比如 each
, map
, filter
, find
, some
等,这些我们平时的业务实现中会经常用到,而有一个 reduce
函数可能经常被忽视。
简单介绍
可直接参考MDN: Array.prototype.reduce
简单来说就是这样的函数形式
1 | [].reduce(function (accumulator, currentValue, currentIndex, array) { |
在迭代函数中会接收一个accumulator
,在 reduce 开始时可以为它设置初始值(即上面的initialValueOfAccumulator
)。在迭代中做的事情就是把处理后的结果追加到accumulator
上再将其返回,这样就使得accumulator
在迭代中依次传递(第0次的结果会传给第1次,第1次的结果传递给第2次,…),所有的处理结果都汇聚在accumulator
上。最后 reduce 函数的返回值就是最终的accumulator
值。
map-reduce 关系
MapReduce 是在分布式中提出的计算方法,我看过两篇解释简单又清晰的文章,可供参考
js 中也有map
和reduce
函数,用它俩也能实现 MapReduce,区别是 js 没有分布式计算的支持。这里简单示意下 word count 程序
1 | // 一些列文章,数组中每个元素相当于一篇文章的完整字符串 |
运用场景举例
1. 数组扁平化
题目:将形如 [[0, 1], [2, 3, 4], [5]]
的数组转成扁平结构的一维数组 [0, 1, 2, 3, 4, 5]
。
用 reduce 实现的话代码就很简单
1 | [[0, 1], [2, 3, 4], [5]].reduce(function (flatten, item) { |
现在考虑一下:如果输入的数组可能嵌套多层呢?形如 [[0, [1]], [2, [3, 4]], [[[5]]]]
且嵌套的深度我们无法预知
1 | // 对多维数组的 flatten |
如果需要从右向左的顺序 flatten 处理,则可使用 reduceRight 代替 reduce
当然,这个多维数组的例子有点刻意使用 reduce 的感觉,只要使用递归,其中 reduce 可以用 tmps = tmps.concat(flatten(array[i]))
代替。
顺便附上 flatten 函数的 underscore 实现版本(非递归实现)
1 | // Internal implementation of a recursive `flatten` function. |
2. 统计节点标签数
另一个实用例子是统计一个页面中所有的节点数,利用 document.getElementsByTagName('*')
可取出所有节点的 HTMLCollection,再配合 map 和 reduce 函数就可轻松统计出各 tagName 的数目。
1 | Array.prototype.slice.call(document.getElementsByTagName('*')) |
reduce 内部实现
根据 reduce 函数的定义,可以简单这样实现(仅供学习使用)
1 | Array.prototype.reduce = function (iterator, accumulator) { |
reduce 函数是 es5 中的标准,从 MDN 上抄了一份 Polyfill(可用于生产环境)
1 | // Production steps of ECMA-262, Edition 5, 15.4.4.21 |