Array.prototype.reduce 是 JavaScript 中比较实用的一个函数,但是很多人都没有使用过它,因为 reduce 能做的事情其实 forEach 或者 map 函数也能做,而且比 reduce 好理解。但是 reduce 函数还是值得去了解的。

reduce 函数可以对一个数组进行遍历,然后返回一个累计值,它使用起来比较灵活,下面了解一下它的用法。

reduce 接受两个参数,第二个参数可选:

@param {Function} callback 迭代数组时,求累计值的回调函数
@param {Any} initVal 初始值,可选

其中,callback 函数可以接受四个参数:

@param {Any} acc 累计值
@param {Any} val 当前遍历的值
@param {Number} key 当前遍历值的索引
@param {Array} arr 当前遍历的数组

callback 接受这四个参数,经过处理后返回新的累计值,而这个累计值会作为新的 acc 传递给下一个 callback 处理。直到处理完所有的数组项。得到一个最终的累计值。

reduce 接受的第二个参数是一个初始值,它是可选的。如果我们传递了初始值,那么它会作为 acc 传递给第一个 callback,此时 callback 的第二个参数 val 是数组的第一项;如果我们没有传递初始值给 reduce,那么数组的第一项会作为累计值传递给 callback,数组的第二项会作为当前项传递给 callback。

示例:

对数组求和:

let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v);
console.log(res); // 6

如果我们传递一个初始值:

let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v, 94);
console.log(res); // 100

利用 reduce 求和比 forEach 更加简单,代码也更加优雅,只需要清楚 callback 接受哪些参数,代表什么含义就可以了。

我们还可以利用 reduce 做一些其他的事情,比如对数组去重:

let arr = [1, 1, 1, 2, 3, 3, 4, 3, 2, 4];
let res = arr.reduce((acc, v) => {
if (acc.indexOf(v) < 0) acc.push(v);
return acc;
}, []);
console.log(res); // [1, 2, 3, 4]

统计数组中每一项出现的次数:

let arr = ['Jerry', 'Tom', 'Jerry', 'Cat', 'Mouse', 'Mouse'];
let res = arr.reduce((acc, v) => {
if (acc[v] === void 0) acc[v] = 1;
else acc[v]++;
return acc;
}, {});
console.log(res); // {Jerry: 2, Tom: 1, Cat: 1, Mouse: 2}

将二维数组展开成一维数组:

let arr = [[1, 2, 3], 3, 4, [3, 5]];
let res = arr.reduce((acc, v) => {
if (v instanceof Array) {
return [...acc, ...v];
} else {
return [...acc, v];
}
});
console.log(res); // [1, 2, 3, 3, 4, 3, 5]

由此可以看出,reduce 函数还是很实用的,但是 reduce 函数兼容性不是特别好,只支持到 IE 9,如果要在 IE 8 及以下使用的话就不行了,所以我们可以自己实现一下,还可以对其做一下扩展,使其能够遍历对象。

首先可以实现一个最基础的 each 函数,作为我们 reduce 的基础:

/**
* 遍历对象或数组,对操作对象的属性或元素做处理
* @param {Object|Array} param 要遍历的对象或数组
* @param {Function} callback 回调函数
*/
function each(param, callback) {
// ...省略参数校验
if (param instanceof Array) {
for (var i = 0; i < param.length; i++) {
callback(param[i], i, param);
}
} else if (Object.prototype.toString.call(param) === '[object Object]') {
for (var val in param) {
callback(param[val], val, param);
}
} else {
throw new TypeError('each 参数错误!');
}
}

可以看出 each 可以遍历对象或数组,回调函数接受三个参数:

@param {Any} v 当前遍历项
@param {String|Number} k 当前遍历的索引或键
@param {Object|Array} o 当前遍历的对象或者数组

有了这个基础函数,我们可以开始实现我们的 reduce 函数了:

/**
* 迭代数组、类数组对象或对象,返回一个累计值
* @param {Object|Array} param 要迭代的数组、类数组对象或对象
* @param {Function} callback 对每一项进行操作的回调函数,接收四个参数:acc 累加值、v 当前项、k 当前索引、o 当前迭代对象
* @param {Any} initVal 传入的初始值
*/
function reduce(param, callback, initVal) {
var hasInitVal = initVal !== void 0;
var acc = hasInitVal ? initVal : param[0];
each(hasInitVal ? param : Array.prototype.slice.call(param, 1), function(v, k, o) {
acc = callback(acc, v, k, o);
});
return acc;
}

可以看到,我们的 reduce 函数就是在 each 上面封装了一层。根据是否传递了初始值 initVal 来决定遍历的起始项。每次遍历都接受 callback 返回的 acc 值,然后在 reduce 的最后返回 acc 累计值就可以啦!

当然,这部分代码有一个很严重的 bug,导致了我们的 polyfill 毫无意义,那就是遍历对象时的 for...in。这个语法和在 IE <= 9 环境下存在 bug,会无法获得对象的属性值,这就导致我们所实现的 reduce 无法在 IE 9 以下遍历对象,但是遍历数组还是可以的。对于 for...in 的这个 bug,可以参考 underscore 是怎么实现的,这里暂时不研究了~

Array.prototype.reduce 的理解与实现的更多相关文章

  1. Array.prototype.reduce()

    reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. arr.reduce([callback, initialValue]) c ...

  2. js Array​.prototype​.reduce()

    例子: , , , ]; const reducer = (accumulator, currentValue) => accumulator + currentValue; // 1 + 2 ...

  3. [JavaScript] Array.prototype.reduce in JavaScript by example

    Let's take a closer look at using Javascript's built in Array reduce function. Reduce is deceptively ...

  4. 数组的方法之(Array.prototype.reduce() 方法)

    reduce函数 reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值. 对数组中的所有元素调用指定的回调函数.该回调函数的返回值为累积结果,并且此返回值在下一次 ...

  5. 数组方法 Array.prototype

    Object.prototype 数组的值是有序的集合,每一个值叫做元素,每一个元素在数组中都有数字位置编号,也就是索引,js中数组是弱类型的,数组中可以含有不同类型的元素.数组元素甚至可以是对象或者 ...

  6. 来自数组原型 Array.prototype 的遍历函数

    1. Array.prototype.forEach() forEach() 是一个专为遍历数组而生的方法,它没有返回值,也不会改变原数组,只是简单粗暴的将数组遍历一次  参数: callback() ...

  7. Array.prototype

    Array.prototype  属性表示 Array 构造函数的原型,并允许您向所有Array对象添加新的属性和方法. /* 如果JavaScript本身不提供 first() 方法, 添加一个返回 ...

  8. [转] 对Array.prototype.slice.call()方法的理解

    在看别人代码时,发现有这么个写法:[].slice.call(arguments, 0),这到底是什么意思呢? 1.基础 1)slice() 方法可从已有的数组中返回选定的元素. start:必需.规 ...

  9. 理解Array.prototype.slice.call(arguments)

    在很多时候经常看到Array.prototype.slice.call()方法,比如Array.prototype.slice.call(arguments),下面讲一下其原理: 1.基本讲解 1.在 ...

随机推荐

  1. jquery插件导出word:jquery.wordexport.js

    前言 今天项目中遇到一个需求把我们系统中的统计数据导出来(主要是表格).其实实现的的方法有很多,而此次针对我的系统第一获取数据有点慢,加上前不久写了一个在线阅读pdf,故此这次也想用前端的方式来导出. ...

  2. 使用 NodeJS+Express+MySQL 实现简单的增删改查

    关于node.js暂时记录如下,以后有时间一定学习 文章来自简书,作者:sprint,2016-07 使用 Node.js + Express+MySQL 实现简单的增删改查 https://www. ...

  3. Linq in条件查询

    Linq 实现sql中的not in和in条件查询   T-SQL的IN: Select ProductID, ProductName, CategoryID From dbo.Products Wh ...

  4. sgsdg

    wrjow we wetwer werwer werwer werqw qweqwrq qwrqwr @ApiOperation("根据条件分页查询试卷") @ApiRespons ...

  5. Wannafly挑战赛9 E - 组一组

    链接:https://www.nowcoder.net/acm/contest/71/E来源:牛客网 题目描述 有一个长为 n 的数列 A,其中有 m 个限制条件,条件有两种: 1.对于区间 [l,r ...

  6. Vue2入门路线及资源

    前言:最近在学习Vue,感觉对vue+vuex+vue-router算是小小地入门了.想起前期最苦恼也是最费时的事,就是在每个阶段找到合适当前水平的资源或者demo,所以本文我根据我自己的体验,整理了 ...

  7. Oracle数据库拼接字符串

    Oracle数据库中字符串拼接的实现 select count() from THT_HTFKJL where ht=1: 假如结果为:31.例如上面例子想要给结果拼接字符串有二种实现方法,第一种使用 ...

  8. Java基础学习—思维导图

    找到两张Java学习的思维导图,特别适合我这样的菜鸟学习,贴过来和小伙伴分享.

  9. Linux 网络流量查看 Linux ip traffic monitor

    Network monitoring on Linux This post mentions some linux command line tools that can be used to mon ...

  10. srop实战

    前言 srop 的作用比较强,在条件允许的情况下,尽量使用它.题目来自于 i春秋的一个比赛. 题目链接: https://gitee.com/hac425/blog_data/blob/master/ ...