定义

js中数组的方法非常重要,对数组的方法一定不能陌生,reduce方法很好地体现了“函数式”理念。

arr.reduce(callback[, initialValue])

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

简单使用

数组的reduce方法接收两个参数
第一个参数为一个函数,函数有四个参数,是total、currentValue、currentIndex、arr。分别代表:

  • total:初始值, 或者计算结束后的返回值。
  • currentValue:当前元素。
  • currentIndex:当前元素的索引。
  • arr:当前元素所属的数组对象。

第二个参数为可选参数 initialValue,代表传递给函数的初始值。

说了这么多,直接来一段代码就懂了。

// 不传第二个参数的情况
var numbers = [1, 2, 3, 4]

function myFunction(item) {
    let result = numbers.reduce(function (total, currentValue, currentIndex, arr) {
        console.log(total, currentValue, currentIndex, arr)
        return total + currentValue
    })
    return result
}

myFunction(numbers)

代码运行后输出:

java中使用reduce和js中的reduce_reduce


可以看到如果不传第二个参数 initialValue,则函数的第一次执行会将数组中的第一个元素作为total参数返回。一共执行3次。

下面是传递第二个参数的情况:

// 不传第二个参数的情况
var numbers = [1, 2, 3, 4]

function myFunction(item) {
    let result = numbers.reduce(function (total, currentValue, currentIndex, arr) {
        console.log(total, currentValue, currentIndex, arr)
        return total + currentValue
    }, 10)
    return result
}

myFunction(numbers)

代码运行后输出:

java中使用reduce和js中的reduce_JavaScript_02


如果传了第二个参数 initialValue,那么第一次执行的时候total的值就是传递的参数值,然后再依次遍历数组中的元素。执行4次。

总结:如果不传第二参数 initialValue,那么相当于函数从数组第二个值开始,并且将第一个值最为第一次执行的返回值,如果传了第二个参数 initialValue,那么函数从数组的第一个值开始,并且将参数 initialValue 作为函数第一次执行的返回值。

应用场景

第一个场景,按顺序执行promise。
实现一个方法,方法内传入一个数组,数组中每一项都返回一个Promise对象。要求按顺序执行数组中每一个Promise。

const fn1 = () => {
	return new Promise((resolve, reject) => {
       setTimeout(() => {
            console.log('fn1')
            resolve(1)
        }, 2000)
    })
}
	
const fn2 = () => {
	return new Promise((resolve, reject) => {
	    setTimeout(() => {
	        console.log('fn2')
	        resolve(2)
	    }, 1000)
	})
}

const arr = [fn1, fn2]

const excPromiseInOrder = (array, value) => {
    array.reduce(
        (prePromise, curPromise) => prePromise.then(curPromise),
        Promise.resolve(value)
    )
}

excPromiseInOrder(arr, 'init')

代码执行结果:
2秒后输出"fn1",再过1秒输出"fn2"
如果对reduce的使用比较了解,那么这段代码也很容易看懂。唯一需要理解的可能就是excPromiseInOrder 函数体内reduce调用。在reduce回调函数中返回promise.then(),使用了链式调用。

第二个场景,Koa中only模块实现
only方法返回一个经过指定筛选属性的新对象。效果如下:

var p = {
    name: 'BuzzLy',
    age: 25,
    email: 'dddd',
    _id: '12345'
}
only(p, ['name', 'email'])   // {name: 'BuzzLy', email: 'dddd',}
only(p, 'name age')   // {name: 'BuzzLy', age: 25,}

其中的实现使用的就是reduce,尝试使用reduce实现一个only方法。

var only = function (obj, keys) {
    obj = obj || {}
    if ('string' == typeof keys) keys = keys.split(/ +/)
    return keys.reduce(function (newO, key) {
        if (null == obj[key]) return newO
        newO[key] = obj[key]
        return newO
    }, {})
}

第三个场景,pipe的实现
pipe 的实现也是reduce 的一个典型应用,pipe是一个 curry 化函数,curry 化函数是一种由接受多个参数的函数转化为一次只接受一个参数的函数,如果一个函数需要3个参数,那curry化后的函数会接受一个参数并返回一个函数来接受下一个函数,这个函数返回的函数去传如第三个参数,最后一个函数会应用了所有参数的函数结果。
pipe的实现代码如下:

function pipe(...functions) {
	return function(input) {
		functions.reduce(
			(preVal, fn) => fn(preVal), 
			input
		)
	}
}

验证

const f1 = x => {
    console.log('执行了f1')
	return x + 1
}

const f2 = x => {
    console.log('执行了f2')
    return 2 * x
}

let result = pipe(f1, f2)(1)
console.log(result) // 4

正确输出4。

当然还有很多应用场景,在这不一一举例。明白其原理便可举一反三。

如何实现一个reduce

看了这么多使用的例子,回过来思考下如何自己实现一个reduce函数呢。
其中的核心就是数组的遍历,并且要通过是否传入第二个参数来判断遍历的起始值,并将得到的参数传入回调函数中执行。
代码如下:

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
}

var arr = [1, 2, 3, 4]
arr.reduce((total, currentValue, currentIndex, arr) => {
    console.log(total, currentValue, currentIndex, arr)
    return total + currentValue
}, 10)

执行后结果为:

java中使用reduce和js中的reduce_js_03


到这,一个reduce函数就实现了。