JavaScript高阶函数浅析

143 阅读5分钟

引言

在JavaScript中,函数是一种特殊类型的对象,它们是Function objects。那么什么是高阶函数呢?

高阶函数

高阶函数的定义很简单,就是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

也就是说高阶函数是对其他函数进行操作的函数,可以将它们作为参数传递,或者是返回它们。简单来说,高阶函数是一个接收函数作为参数传递或者将函数作为返回值输出的函数

函数作为参数传递

JavaScript语言中内置了一些高阶函数,比如:Array.prototype.map,Array.prototype.filter和 Array.prototype.reduce,它们接受一个函数作为参数,并应用这个函数到列表的每一个元素。我们来看看使用它们与不使用高阶函数的方案对比。

Array.prototype.map

map()方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果,原始数组不会改变。传递给map的回调函数(callback)接受三个参数,分别是currentValue、index(可选)、array(可选),除了callback之外还可以接受this值(可选),用于执行callback函数时使用this值。

来个简单的例子方便理解:现在有一个数组[1,2,3,4],我们想要生成一个新数组,其每个元素皆是之前数组的两倍,那么我们右下面两种使用高阶和不使用高阶函数的方式来实现。

不使用高阶函数

const arr1 = [1,2,3,4];
const arr2 = [];
for(let i = 0; i<arr1.length; i++) {
    arr2.push(arr1[i] * 2);
}
console.log(arr2); // [2,4,6,8]
console.log(arr1); // [1,2,3,4]

使用高阶函数

const arr1 = [1,2,3,4];
const arr2 = arr1.map(item => item * 2);
console.log(arr2); // [2,4,6,8]
console.log(arr1); // [1,2,3,4]

Array.prototype.filter

filter()方法创建一个新数组,其包含通过提供函数实现的测试的所有元素,原始数组不会改变。接收的参数和map是一样的,其返回值是一个新数组、由通过测试的所有元素组成,如果没有任何数组元素通过测试,则返回空数组。

来个例子介绍下:现在有一个数组[1,2,1,2,3,5,4,5,3,4,4,4,4],我们想要生成一个新数组,这个数组要求没有重复的内容,即去重。

不使用高阶函数

const arr1 = [1,2,1,2,3,5,4,5,3,4,4,4,4];
const arr2 = [];
for(let i = 0; i<arr1.length; i++) {
    if(arr1.indexOf( arr1[i] ) === 1) {
        arr2.push( arr1[i] )
    }
}
console.log(arr2); // [1,2,3,5,4];
console.log(arr1); 
// [1,2,1,2,3,5,4,5,3,4,4,4,4];

使用高阶函数

const arr1 = [1,2,1,2,3,5,4,5,3,4,4,4,4];
const arr2 = arr.filter( (element,index,self) => {
    return self.indexOf(element) === index;
});
console.log(arr2); // [1,2,3,5,4];
console.log(arr1); 
// [1,2,1,2,3,5,4,5,3,4,4,4,4];

Array.prototype.reduce

reduce()方法对数组中的每个元素执行一个提供的reduce函数(升序执行),将其结果汇总为单个返回值。传递给reduce的回调函数(callback)接受四个参数,分别是 累加器accumulator、currentValue、currentIndex(可选)、array(可选),除了callback之外还可以接受初始值initialValue值(可选)。

  • 如果没有提供initialValue,那么第一次调用callback函数时,accumulator使用原数组中的第一个元素,currentValue既是数组中的第二个元素。在没有初始值的空数组上调用reduce将报错。
  • 如果提供了initialValue,那么将作为第一次调用callback函数时的第一个参数的值,即accumulator,currentValue使用原数组中的第一个元素。

来个简单的例子介绍下:现在有一个数组[0,1,2,3,4],需要计算数组元素的和,需求比较简单,来看下代码实现。

不使用高阶函数

const arr = [0,1,2,3,4];
let sum = 0;
for(let i = 0; i<arr.length; i++) {
    sum += arr[i];
}
console.log(sum); // 10
console.log(arr); // [0,1,2,3,4]

使用高阶函数

无initialValue值

const arr = [0,1,2,3,4];
let sum = arr.reduce( (accumulator,currentValue,currentIndex,array) => {
    return accumulator + currentValue;
});
console.log(sum); // 10
console.log(arr); // [0,1,2,3,4]

上面是没有initialValue的情况,代码的执行过程如下,callback总共调用了四次。

1653552872321.jpg

有initialValue值 我们再来看下有initialValue的情况,假设initialValue值为10,我们看下代码。

const arr = [0,1,2,3,4];
let sum = arr.reduce( (accumulator,currentValue,currentIndex,array) => {
    return accumulator + currentValue;
},10);
console.log(sum); // 20
console.log(arr); // [0,1,2,3,4]

代码的执行过程如下所示,callback总共调用了五次。

1653553127538.jpg

函数作为返回值输出

这个很好理解,就是返回一个函数,下面直接看两个例子来加深理解。

isType函数

我们知道在判断类型的时候可以通过Object.prototype.toString.call来获取对应对象返回的字符串,比如:

let isString = obj => Object.prototype.toString.call( obj ) === '[object String]';

let isArray = obj => Object.prototype.toString.call( obj ) === '[object Array]';

let isNumber = obj => Object.prototype.toString.call( obj ) === '[object Number]'

可以发现上面三行代码中有很多重复代码,只需要把具体的类型抽离出来就可以封装成一个判断类型的方法了,代码如下:

let isType = type => obj => {
    return Object.prototype.toString.call( obbj ) === '[object' + type + ']';
}
isType('String')('123') // true
isType('Array')([1,2,3]) // true
isType('Number')(123) // true

这里就是一个高阶函数,因为isType函数将 obj => {...}这一函数作为返回值输出。

add函数

我们看一个常见的面试题:用JS实现一个无限累加的函数add,示例如下:

add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10

// 以此类推

我们可以看出结构和上面代码有些类似,都是将函数作为返回值输出,然后接收新的参数并进行计算。

我们知道打印函数时会自动调用toString()方法,函数add(a)返回一个闭包sum(b),函数sum()中累加计算 a = a+b,只需要重写sum.toString()方法返回变量a 就可以了。

function add(a) {
    function sum(b) { // 使用闭包
        a = a+b; // 累加
        return sum;
    }
    sum.toString = function() { 
    // 重写toString方法
        return a;
    }
    return sum; // 返回一个函数
}
add(1); // 1
add(1)(2); // 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10