数组 | 强大的reduce(含手写原理)

reduce相关语法

1
arr.reduce(callbackFn, [initalValue]);

callbackFn:reducer函数

包含四个参数:

  • previousValue:上一次调用callbackFn的返回值,在第一次调用的时候,如果指定了initalValue,则其值为initalValue,否则为数组索引为0的元素
  • currentValue:数组正在处理的元素,在第一次调用的时候,如果指定了initalValue,其值为数组索引为0的元素,否则为数组索引为1的元素
  • currentIndex:数组正在处理的元素的索引,如果指定了initalValue,起始索引为0,否则为1
  • array:用于遍历的数组

initalValue(可选):

  • 第一次调用时作为previousValue的值,这是一个可选的参数

注意点

  • reducer不会改变原数组
  • reducer中的callbackFn需要返回值(因为需要作为下一次调用的previousValue)

reduce源码实现

实现思路

  • 类型检验:回调函数是否对函数,数组是否为空等
  • 初始值提供检测,用于确定初始previousValue的值
  • 返回累计值

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Array.prototype.myReduce = function(callbackFn, initalValue) {
// Step1:类型检验
if (this === null) {
throw new TypeError('Array.prototype.reduce called on null or undefined');
};

if (typeof callbackFn !== 'function') {
throw new TypeError('Callback must be a function');
};

const obj = Object(this); // #1
const lenValue = obj.length;
const len = lenValue >>> 0; // #2
if (len === 0 && !initalValue) {
throw new TypeError('The array contains no elements and initalValue is not provided')
};

let k = 0;
let accumulator; // 这里变量名为accumulator十分贴合reduce函数作为累加器使用的性质
// Step2:初始accumulator的值确定
if (initalValue) {
// 有initalValue,accumulator就为初始值
accumulator = initalValue;
} else {
// 没有initalValue,就需要把数组的第一个值即arr[0]作为初始值
// 这里别着急,我们还需要做进一步检测
let kPressent = false; // #3
while (!kPressent && k < len) {
const pK = String(k);
kPressent = obj.hasOwnProperty(pK); // 第一个检测到合法的元素作为
if (kPressent) {
accumulator = obj[pK];
};
k++;
};
if (!kPressent) { // 数组没有合法元素,报错
throw new TypeError('The array contains error elements');
};
};

// 到这里我们已经确定了accumulator的值
// 注意:如果initalValue存在,k=0,如果不存在,则accumulator = 数组第一个合法元素,k因为之前也++了,k=k

// Step3:确定callback的返回值
while (k < len) {
if (k in obj){ // # 4
accumulator = callbackFn(accumulator, obj[k], k, obj);
};
k++;
};
return accumulator;
};



// test:
const arr = [1,2,3,4,5];
const sum = arr.myReduce((prev, curr) => prev+curr, 0);
console.log(sum); // 15

说明

  • #1:Object(this)

    Object构造函数作为一个工厂函数,能够根据传入的值的类型返回相应原始值包装类型的实例

    • null或者undefined:将会创建并返回一个空对象
    • 基本类型的值:构造其包装类型的对象
    • 引用类型的值:仍然返回这个值,经他们复制的变量保有和源对象相同的引用地址
  • #2:len = lenValue >>> 0

>>>表示无符号右移,为了保证结果为非负整数

  • #3: 获取数组第一个有效元素

    我觉得这里主要为了检测空值,虽然之前说了若是没有initalValue,previousValue就为array[0],但是若出现数组前面有空值或者全部为空值的情况,previous的取值是需要慎重的,所以初始化previousValue准确地说应该是数组中合法的第一个元素

  • #4:跳过空值(补课:空值检测以及reduce对存在空值的数组处理

reduce应用场景

累和&&累积

1
2
3
4
let arr = [1,2,3,4,5];

let sum = arr.reduce((prev, curr) => prev+curr, 0); // 15
let mul = arr.reduce((prev, curr) => prev*curr, 1); // 120

求最大值/最小值

1
2
3
4
let arr = [2,4,7,3,5,8,10,9];

let maxValue = arr.reduce((prev, curr) => Math.max(prev, curr)); // 10
let minValue = arr.reduce((prev, curr) => Math.min(prev, curr)); // 2

数组去重

1
2
3
4
5
6
7
8
let arr = [3,7,4,2,5,3,2,8];

let newArr = arr.reduce((prev, curr) => {
if (prev.indexOf(curr) === -1) {
prev.push(curr);
};
return prev;
}, []); // [3, 7, 4, 2, 5, 8]

实现map函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .map(callback)  callback = function(item, index, arr) {...}
Array.prototype.myMap = function(callback) {
if(typeof callback === 'function') {
return this.reduce((prev, item, index, arr) => {
prev.push(callback(item, index, arr));
return prev;
}, [])
} else {
throw new Error('callback is not a fucntion');
};
};

// test
let arr = [1,2,3,4,5];
let newArr = arr.myMap(item => item*2); // [2,4,6,8,10]

实现filter函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// .filter(callback)  callback(item, index, arr) {...}

Array.prototype.myFilter = function(callback) {
if (typeof callback === 'function') {
return this.reduce((prev, item, index, arr) => {
if (callback(item, index, arr)) {
prev.push(item);
};
return prev;
}, [])
} else {
throw new Error('callback is not a fucntion');
}
};

// test
let arr = [1,2,3,4,5];
let filterArr = arr.myFilter(item => item > 3); // [4,5]

实现compose函数

compose函数是指将函数按顺序执行,将若干个函数组合成一个函数来执行,并且每个函数执行的结果都能作为下一个函数的参数

假设有这样两个函数,一个求和函数,一个累积函数

1
2
3
4
5
6
7
function sum(value) {
return value += 20;
}

function mul(value) {
return value *= 10;
};

一般情况下,会这样使用:

1
2
3
4
let value = 10;
let res = sum(value);
res = mul(res);
console.log(res); // 300

若是有compose函数,将可以实现以下效果:

1
2
3
4
5
6
7
8
9
10
const fn = compose(sum, mul);
console.log(fn(value)); // 300

function compose() {
let args = [...arguments];

return function(x) {
return args.reduce((prev, curr) => curr(prev),x)
};
};

按顺序执行Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input)
)
}

// promise function 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5)
})
}

// promise function 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2)
})
}

// function 3 - will be wrapped in a resolved promise by .then()
function f3(a) {
return a * 3
}

// promise function 4
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4)
})
}

const promiseArr = [p1, p2, f3, p4]
runPromiseInSequence(promiseArr, 10)
.then(console.log) // 1200

实现数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// flat(depth) 
function myFlat(arr, depth) {
if (depth < 1) return arr;
if (Array.isArray(arr)) {
return arr.reduce((prev, cur) => {
return Array.isArray(cur) ? prev.concat(myFlat(cur, depth-1)) : prev.concat(cur);
}, [])
} else {
throw new Error('this is not an array');
}
};

// test
const arr = [1, 2, [3,4, [5]], [6, 7], [8,[9, [10]]]]
myFlat(arr, 2) // [1, 2, 3,4, 5, 6, 7, 8,9, [10]]

统计数组元素出现次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function count(arr) {
return arr.reduce((prev, curr) => {
if (prev.has(curr)) {
prev.set(curr, prev.get(curr)+1);
} else {
prev.set(curr, 1);
};
return prev;
}, new Map())
};


// test
let arr = [1,2,3,1,2,4,5,6,5,6,1,2,3];
let m = count(arr); // {1=>3, 2=>3, 3=>3, 4=>1, 5=>5, 6=>2}

使用函数组合实现管道

1
2
3
4
5
6
7
8
9
10
function increment(input) { return input + 1;}
function decrement(input) { return input - 1; }
function double(input) { return input * 2; }
function halve(input) { return input / 2; }

let pipeline = [increment, double, decrement];

const result = pipeline.reduce(function(total, func) {
return func(total);
}, 8); // 17

知识补充

空值检测

原始值包装类型

参考

MDN reduce

你应该知道的JS: reduce的n种应用

6个关于Reduce() 应用场景的用例

JavaScript之Array.reduce源码解读