Array.prototype.reduce 的理解与实现
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 的理解与实现的更多相关文章
- Array.prototype.reduce()
reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. arr.reduce([callback, initialValue]) c ...
- js Array.prototype.reduce()
例子: , , , ]; const reducer = (accumulator, currentValue) => accumulator + currentValue; // 1 + 2 ...
- [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 ...
- 数组的方法之(Array.prototype.reduce() 方法)
reduce函数 reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值. 对数组中的所有元素调用指定的回调函数.该回调函数的返回值为累积结果,并且此返回值在下一次 ...
- 数组方法 Array.prototype
Object.prototype 数组的值是有序的集合,每一个值叫做元素,每一个元素在数组中都有数字位置编号,也就是索引,js中数组是弱类型的,数组中可以含有不同类型的元素.数组元素甚至可以是对象或者 ...
- 来自数组原型 Array.prototype 的遍历函数
1. Array.prototype.forEach() forEach() 是一个专为遍历数组而生的方法,它没有返回值,也不会改变原数组,只是简单粗暴的将数组遍历一次 参数: callback() ...
- Array.prototype
Array.prototype 属性表示 Array 构造函数的原型,并允许您向所有Array对象添加新的属性和方法. /* 如果JavaScript本身不提供 first() 方法, 添加一个返回 ...
- [转] 对Array.prototype.slice.call()方法的理解
在看别人代码时,发现有这么个写法:[].slice.call(arguments, 0),这到底是什么意思呢? 1.基础 1)slice() 方法可从已有的数组中返回选定的元素. start:必需.规 ...
- 理解Array.prototype.slice.call(arguments)
在很多时候经常看到Array.prototype.slice.call()方法,比如Array.prototype.slice.call(arguments),下面讲一下其原理: 1.基本讲解 1.在 ...
随机推荐
- Head First Python学习笔记4——处理数据
有这么几组数据需要你处理: James 2-34,3:21,2.34,2.45,3.01,2:01,2:01,3:10,2-22 Julia 2.59,2.11,2:11,2:23,3-10,2-23 ...
- JavaScript学习笔记系列2:Dom操作(一)
一.什么是Dom? DOM------>Document Object Model 直接翻译就是文档对象模型. DOM------>定义了表示和修改文档所需的对象.这些对象的行为和属性以及 ...
- C# list的合并
转自:https://www.cnblogs.com/liguanghui/archive/2011/11/09/2242309.html List<int> listA = new Li ...
- [日常] 编写HTTP接口文档
一.什么是接口文档?在项目开发中,web项目的前后端分离开发,APP开发,需要由前后端工程师共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发,到项目结束前都要一直维护.二.为什么要写接口 ...
- C语言读取配置文件
自从大学学完C之后,就再也没用过它了, 在网上找代码,七拼八凑之后,终于成形~~勉强能用,不喜勿喷,^_^! int GetValue(const wchar_t *key, wchar_t *val ...
- Sql Server 中使用日期遍历
一个存储过程小案例,内容如下: declare @dt datetime set @dt='2016-01-01' while (@dt<='2016-12-31') begin -- 转换字符 ...
- Spring系列之——springboot解析resources.application.properties文件
摘要:本文通过讲解如何解析application.properties属性,介绍了几个注解的运用@Value @ConfigurationProperties @EnableConfiguration ...
- ZooKeeper 节点
ZNode zk 中的每一个数据节点称为 ZNode ,所有的 ZNode 按层次化结构进行组织,形成一棵树(与Linux文件系统相似).节点可以写入数据,也可以在节点下面创建子节点. 节点类型: 1 ...
- 《JavaWeb从入门到改行》JDBC经典秘方QueryRunner
目录: 基础篇_功能各自回顾 JDBC基础代码回顾(使用JdbcUtils工具简化) c3p0数据库连接池的使用(使用JdbcUtils工具简化) 大数据的插入(使用c3p0+JdbcUtils工具简 ...
- CCScrollView的滑动体验
最近在研究cocos2dx,由于项目中要用到内容滚动效果(内容超出容器,可以通过滑动屏幕滚动来查看),所以就毫不犹豫的选择了CCScrollView组件. 使用中发现CCScrollView的体验 ...