高阶函数
抽象、重复的抽象、可组合性
抽象
看下面的代码,我们将代码封装成函数,它的好处是什么呢?
let total = 0, count = 1;
while(count <= 10) {
total += count;
count = 1;
}
console.log(total)
上面是一个简单的累加代码,命令式的编写代码,再简单逻辑的时候我们能够很清楚在干什么,但是一旦变量增多,代码行增加,逻辑复杂起来,我们就很难一下子明白这段代码是在干什么。
console.log(sum(range(1, 10)))
如果我们用两个函数来完成我们的任务,只需要一行代码并且很清楚我们是在求1-10的和。并且下次我们仍然可以复用sum
和range
函数。sum
和range
这两个函数定义的操作当然会包含循环、计数和其他一些操作。但相比于将这些代码直接写到一起,这种表述方式更为简单,同时也易于避免错误。
那我们是如何将上面的命令式代码抽象出函数的呢?
任务1:计算1-10的和
- 需要一个计算和的函数 - 目标求和
function sum(start, end) {
let total = 0
for(; start <= end; start++) {
total += start
}
return total;
}
OK,非常完美,而且一个函数就搞定了。而且还能通过参数控制求和范围,具有可扩展性,复用性。
嗯,这确实做到了抽象…
重复的抽象
任务2:现在需要重复十次打印console.log()
function repeat(n) {
for(let i = 0; i < n; i++ ) {
console.log(...)
}
}
repeat(10);
任务3:现在我需要重复十次请求网络资源
function repeatFetch(n) {
for(let i = 0; i < n; i++ ) {
fetch(url, options).then(...)
}
}
现在一看我们封装的函数好像并不够抽象。
我们一直注意次数,却忽略了重复的事情。
再次抽象:我们需要重复的函数,只完成重复n次而不需要关系重复的事情。
function repeat(n, action) {
for(let i = 0; i < n; i++ ) {
action(i)
}
}
repeat(3, console.log)
Perfact!
这样一来,我们就不需要写一堆repeat*的函数了。
优雅的使用:
let labels = [];
repeat(5, i => {
labels.push(`chapter ${i + 1}`);
});
console.log(labels);
// → ["chapter 1", "chapter 2", "chapter 3", "chapter 4", "chapter 5"]
可组合性
将最基础的函数抽象,让其他函数作为插件使用,扩展我们的基础函数的功能,极大的增强了我们函数的扩展性。
function noisy(f) {
return (...args) => {
console.log("calling with", args);
let result = f(...args);
console.log("called with", args, ", returned", result);
return result;
};
}
noisy(Math.min)(3, 2, 1);
// → calling with [3, 2, 1]
// → called with [3, 2, 1] , returned 1
这种思想在非常多的地方体现,无论是我们使用的框架、打包工具等等。
高阶函数、函数柯里化