一个函数 如果输入参数包含函数 或 返回值是函数,就称为高阶函数。

这篇文章介绍高阶函数的一个子集:输入 fn,输出 fn'。

按fn与fn'功能是否一致【即相同输入是否始终对应相同输出】,把这类高阶函数的作用分为两类:

包装函数:功能一致

修改函数:功能不一致

包装函数

从斐波那契数列开始。

const fib = n => (n <= 1 ? 1 : fib(n - 1) + fib(n - 2));

fib(42);

记录执行时间

普通青年

const fib = n => (n <= 1 ? 1 : fib(n - 1) + fib(n - 2));

const start = new Date().getTime();

fib(42);

console.log(new Date().getTime() - start + "ms");

函数式青年

const timed = fn => (...args) => {

const start = new Date().getTime();

const result = fn(...args);

console.log(new Date().getTime() - start + "ms");

return result;

};

const fib = n => (n <= 1 ? 1 : fib(n - 1) + fib(n - 2));

timed(fib)(42);

性能优化 memorize

普通青年

const memory = {};

const fib = n => {

if (n <= 1) return 1;

else {

if (memory[n]) return memory[n];

else {

memory[n] = fib(n - 1) + fib(n - 2);

return memory[n];

}

}

};

const timed = fn => (...args) => {

const start = new Date().getTime();

const result = fn(...args);

console.log(new Date().getTime() - start + "ms");

return result;

};

timed(fib)(42);

函数式青年

const memorize = fn => {

const memory = {};

return arg => {

if (memory[arg]) return memory[arg];

else {

memory[arg] = fn(arg);

return memory[arg];

}

};

};

const fib = memorize(n => (n <= 1 ? 1 : fib(n - 1) + fib(n - 2)));

const timed = fn => (...args) => {

const start = new Date().getTime();

const result = fn(...args);

console.log(new Date().getTime() - start + "ms");

return result;

};

timed(fib)(42);

修改函数

once

场景:

发送请求,如果后台返回 session 超时,弹出重新登录提示框。

发出多个请求,后台都返回 session 超时错误,只希望弹一个重新登录提示框。

const once = fn => {

let executed = false;

return (...args) => {

if (!executed) {

executed = true;

fn(...args);

}

};

};

const showLogoutWin = once(function() {

// ...

});

debounce

场景:

输入框 change 事件触发向后台查询

为消除不必要的查询

用户连续输入时不触发查询,当 200ms 内没有新的输入时,才向后台查询

const debounce = (fn, ms = 200) => {

let timeoutId;

return (...args) => {

// YOU MAY HAVE A TRY

};

};

更多实际场景

validateRequired

场景:

根据 rule.required 判断空值时是否报错,这段逻辑出现在多个 validator 中。

const ipv4Validator = (rule, value, callback) => {

if (value) {

if (ipv4RegExp.test(value)) {

callback();

} else {

callback("请输入合法IP");

}

} else {

if (rule.required) {

callback("该域为必填项");

} else {

callback();

}

}

};

const validateRequired = (validator, msg = "该域为必填项") => (

rule,

value,

callback

) => {

if (value) {

validator(rule, value, callback);

} else {

if (rule.required) {

callback(msg);

} else {

callback();

}

}

};

const ipv4Validator = validateRequired((rule, value, callback) => {

if (ipV4Regexp.test(value)) {

callback();

} else {

callback("请输入合法IP");

}

});

tryUntilSucceeded

场景:

因为网络不稳定,请求可能出错,出错后重新请求,直到得到响应为止。

let res;

while (true) {

try {

res = await get(path);

break;

} catch (err) {

console.log(err);

}

}

每个请求都套一层while,写起来太费事、太重复。

const tryUntilSucceeded = fn => async (...args) => {

// YOU MAY HAVE A TRY

};

const enhancedGet = tryUntilSucceeded(get);

const enhancedPost = tryUntilSucceeded(post);

const resGet = await enhancedGet(path);

const resPost = await enhancedPost(path);

小结

恰当使用高阶函数有以下好处:

函数做的事情更单一

像上面斐波那契数列的例子,fib 只关心数列的计算逻辑

记录时间、性能优化的事情交给 timed 和 memorized 处理

这样 fib 逻辑简单,不容易出错

代码可复用,减少了代码重复

像上面的timed, memorized......tryUntilSucceeded都可以提取到公共库,供别的地方使用