简述
高阶函数,即对函数进行操作的函数,在 JavaScript
中是一个重要的概念,拥有多种运用,能够理解并合理使用高阶函数,才不失为一个合格的 JavaScript
开发人员。
概念
我们知道,在 JavaScript
中,函数
是一等公民,任何能够使用变量的地方也都能够使用函数代替,即把函数看作具体的值或数据,如
// 数组元素可以是函数
const arr = [() => {}];
// Map Set 的键、值可以是函数
// Map Set 是 ES6 新提出的数据类型
const map = new Map();
map.set(
() => {},
() => {}
); // 添加一个键、值都为函数的键值对
// 将函数作为参数传递
function sayHi() {
console.log('hi, nice to meet you!');
}
setTimeout(sayHi, 1000); // 1 秒后打印:hi, nice to meet you!
// 返回一个函数
function createFunc() {
return () => console.log('悠悠苍天,何薄于我');
}
const print = createFunc();
print(); // 悠悠苍天,何薄于我
可以看到,任何能够使用变量的地方都能够使用函数代替,而高阶函数则可以认为是接收函数作为参数或返回一个函数的函数。
在 JavaScript
中,处处可见高阶函数的身影,如上述示例中的 setTimeout
方法,以及数组的 map
、reduce
、filter
等方法皆是高阶函数。
运用
说完了高阶函数的相关概念,我们再来说说关于高阶函数的运用,关于高阶函数的运用主要有以下几种
内部调用
最容易理解的一种,将函数交由高阶函数调用,在内部传递参数,如数组的 filter
方法
// filter
const arr = [2, 4, 6, 8, 10];
const newArr = arr.filter(
(ele, index, arr) => ele > 5
);
console.log(newArr); // 6 8 10
filter
方法对每个数组元素调用传入的回调方法,根据回调返回值决定是否舍弃数据,我们根据它的特性手动实现一个高阶函数 filter
// 手动实现 filter
function filter(arr, callback, thisArgs) {
const newArr = [];
for (let index = 0; index < arr.length; index++) {
const ele = arr[index];
const result = callback.call(
(thisArgs !== undefined ? thisArgs : this),
ele,
index,
arr
);
if (Boolean(result)) {
newArr.push(ele);
}
}
return newArr;
}
const arr = [2, 4, 6, 8, 10];
const newArr = filter(
arr,
(ele, index, arr) => ele > 5
);
console.log(newArr); // 6 8 10
可以看到效果是相同的。像很多官方 Api 和很多库的作者提供的一些高阶函数,隐藏了内部实现,让我们不知道它做了什么,只知道结果,但我们能够依据它的特性模拟出相似效果。
函数包装
顾名思义,函数包装就是在指定函数外层包装一层函数,有什么用呢?我们思考以下场景:
- 多个函数都需要在调用前执行固有操作
- 多个函数都需要传入固有参数
像以上场景,难道在每次函数调用时都手动书写一次吗?很明显不是,我们能够对指定场景进行一层函数的封装
// 接收一个函数并返回一个包装函数
function createContainer (callback) {
return function (...args) {
let result = null;
// 开启一个进度条
progress.start();
try {
result = callback.apply(this, args);
} finally {
// 结束进度条
progress.stop();
}
return result;
}
}
上述代码定义了一个 createContainer
函数并返回一个包装函数,每次调用这个函数时会开启一个进度条,然后调用指定回调,不管结果如何都在回调执行完毕后关闭进度条。
代码只是示例,有关进度条的场景应该存在于异步任务中,但上述代码没有处理异步的逻辑。
函数柯里化与惰性求值
柯里化:指将多参数函数转换为单参数函数多次调用传参。
惰性求值:一般函数调用,都是立即执行并返回结果的,那如果有这样的需求呢:已有数据当作参数传入函数,但不希望立即得到结果,因为还需要其他数据,但其他数据还未获得,这时我们就可以利用函数柯里化进行数据的惰性求值。
function setName (name) {
return function (age) {
return { name, age };
}
}
const getInfo = setName('yuanyxh');
setTimeout(() => {
const info = getInfo(22);
console.log(info); // 两秒后: { name: 'yuanyxh', age: 22 }
}, 2000);
上述代码是模拟的场景,利用了函数柯里化,先调用 setName
并传入数据,返回一个函数,因为闭包的存在,此时参数 name
并没有被销毁,仍然能够被内部函数访问,在两秒后调用返回的内部函数再次传入数据,达到了惰性求值。
结语
本文理解并讲述了关于 JS
中高阶函数的概念及部分运用,内容有误请指正,内容有缺请补充。