写在前面,由于在B站录制游戏视频玩,导致最近晚上睡得晚,由于不可描述的问题,现在爆困。衰老,生病和死亡,人类本身这台机器还是太不耐操了。唯有克己,将有限的时间,用到有价值的地方。不多闲扯了,进入今天的笔记。
关于使用语法,我们来看菜鸟教程的吧,目前还用不上
array.reduce(function(total,currentValue,currntIndex,arr),initialValue)
函数,必需。用于执行每个数组元素的函数。
函数参数:
total 必需。初始值,或者计算结束后的返回值。
currentValue 必需。当前元素
currentIndex 可选。当前元素的索引。
arr 可选。当前元素所属的数组对象
initialValue。 可选,传递给函数的初始值
举例使用:数组的元素之和
<button onclick="myFunction()">点我</button>
<p>数组元素之和: <span id="demo"></span></p>
<script>
var numbers = [15.5, 2.3, 1.1, 4.7];
function getSum(total, num) {
return total + Math.round(num);
}
function myFunction(item) {
document.getElementById("demo").innerHTML = numbers.reduce(getSum, 0);
}
</script>
确实实现了数组的顺序往下叠加
const f1 = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1 running')
resolve(1)
}, 1000)
})
const f2 = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 running')
resolve(2)
}, 1000)
})
const array = [f1, f2]
const runPromiseInSequence = (array, value) => array.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(value)
)
runPromiseInSequence(array, 'init')
执行结果如下图:

建议小伙伴们去试一下上面的代码,在浏览器中,reduce 确实对promise 进行了一波顺序操控。
reduce 实现 pipe (简单来讲这个是一个管道函数)
reduce 的另外一个典型应用可以参考函数方法 pipe 的实现:pipe(f, g, h) 是一个 curry 化函数,它返回一个新的函数,这个新的函数将会完成(...args)=> h(g(f(...args)))) 的调用。即 pipe 方法返回的函数会接收一个参数,这个参数传递给 pipe 方法第一个参数,以供其调用.
const pipe = (...functions) => input => functions.reduce(
(acc, fn) => fn(acc),
input
)
实际来说知乎对这种做法褒贬不一,如果过分了函数式,会导致代码及其难懂,后面同事看不懂甚至可能会直接导致重构,

https://zhuanlan.zhihu.com/p/52207982
业务中使用相对要少的原因是,第一可能用 for 循环等直接以重复代码的方式写出,好处是后续人接收代码确实容易看懂,坏处就是可能会有重复的代码,且感觉代码量是巨大的。过度的函数包装写法,让人完全看不懂你在写什么,不是不好,需要了解并在恰当的时候使用。 按照我目前的也还没有使用过 reduce
既然了解了使用方法那么我们能否实现一个呢。
实现一个 reduce
来自于 MDN 的 polyfill:
if(!Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function(callback /*, initialValue*/) {
if (this === null) {
throw new TypeError('Array.prototype.reduce' + 'called on null or undefined')
}
if (typeof callback !== 'function') {
throw new TypeError(callback + 'is not a function')
}
var o = Object(this)
var len = o.length >>> 0
var k = 0
var value
if (arguments.length >= 2) {
value = arguments[1]
} else {
while (k < len && !(k in o)) {
k++
}
if ( k >= len ) {
throw new TypeError('Reduce of empty array' + 'with no initial value')
}
value = o[k++]
}
while (k < len) {
if (k in o) {
value = callback(value,o[k], k, o)
}
k++
}
return value
}
})
}
有点懵逼,于是乎我们去MDN 直接看一波
Object
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
Object.defineProperty()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Array.prototype.reduce()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
MDN 三条API 自取
稍微用浏览器测试了一下来自于MDN代码,毕竟大佬把注释全都删除
了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>reduce进行测试</title>
</head>
<body>
<button onclick="myFunction()">点我</button>
<p>数组元素之和: <span id="demo"></span></p>
<script>
// Polyfill
// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
// https://tc39.github.io/ecma262/#sec-array.prototype.reduce
if (Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function(callback /*, initialValue*/) {
if (this === null) {
throw new TypeError( 'Array.prototype.reduce ' +
'called on null or undefined' );
}
if (typeof callback !== 'function') {
throw new TypeError( callback +
' is not a function');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
console.log(Object(this))
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0; // 进行了 无符号位移
console.log('o.length >>> 0 -->', o.length >>> 0)
// Steps 3, 4, 5, 6, 7
var k = 0;
var value;
if (arguments.length >= 2) {
value = arguments[1];
} else {
while (k < len && !(k in o)) {
k++;
}
// 3. If len is 0 and initialValue is not present,
// throw a TypeError exception.
if (k >= len) {
throw new TypeError( 'Reduce of empty array ' +
'with no initial value' );
}
value = o[k++];
}
// 8. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
// i. Let kValue be ? Get(O, Pk).
// ii. Let accumulator be ? Call(
// callbackfn, undefined,
// « accumulator, kValue, k, O »).
if (k in o) {
value = callback(value, o[k], k, o);
}
// d. Increase k by 1.
k++;
}
// 9. Return accumulator.
return value;
}
});
}
var numbers = [15.5, 2.3, 1.1, 4.7];
function getSum(total, num) {
return total + Math.round(num);
}
function myFunction(item) {
document.getElementById("demo").innerHTML = numbers.reduce(getSum, 0);
}
</script>
</body>
</html>
reduce 可以干的事情
数组求和
let arr = [1, 2, 3];
let res = arr.reduce((acc, v) => acc + v);
console.log(res); // 6
数组去重
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]
扩展了一波回来之后,
回归我们的文章代码 使用了 value 作为初始值,并通过 while 循环,依次累加计算出 value 结果并输出。相比于MDN大佬的实现方式是:
Array.prototype.reduce = Array.prototype.reduce || function(func, initialValue) {
var arr = this
var base = typeof initialValue === 'undefined' ? arr[0] : initialValue
var startPoint = typeof initialValue === 'undefined' ? 1 : 0
arr.slice(startPoint)
.forEach(function(val, index) {
base = func(base, val, index + startPoint, arr)
})
return base
}
使用 forEach 替代了 while 实现结果的累加,他们本质趋近相同。 当然 forEach 的方法是 ES5 新增的。因此,用ES5的一个API(forEach),去实现另外一个 ES5 的API并没有什么意义,大佬这个位置还是让大家理解为主,实际上这个位置来讲,我看代码看资料,也看了许久,也许对大佬来讲真的不算什么。但是对此觉得吃力的朋友,我还是还是建议多次反复研究这个api。
通过 Koa only 模块源码认识 reduce
看到这个位置如果基本还能看懂的话,那么基本对 reduce 有点认识了。最后再看一个 Koa 源码的 only 模块,以求加深印象。
var o = {
a: 'a',
b: 'b',
c: 'c'
}
only(o, ['a','b']) // {a: 'a', b: 'b'}
可以看出这个模块基本上来说就是一个筛选模块。
该方法返回一个经过指定筛选属性的新对象。
only 模块实现:
var only = function(obj, keys){
obj = obj || {}
if ('string' == typeof keys) keys = keys.split(/ +/)
return keys.reduce(function(ret, key){
if (null == obj[key]) return ret
ret[key] = obj[key]
return ret
},{})
}
数组reduce就是说到这吧,实话说我惊艳于它对promse 的精准控制,我所知道的API,只有async await 可以做到精准控制这一点。 且不说它的筛选,求和,数组去重,统计出现次数,数组展开,这个api 出来数组数据看起来貌似及其强悍,适合于复杂业务逻辑。例如对个接口并行回调,串联场景。
路漫漫兮修远兮。
吾将上下而求索。
文章采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接。