高阶函数 - Higher Order Function
一个函数如果有 参数是函数 或 返回值是函数,就称为高阶函数。
这篇文章介绍高阶函数的一个子集:输入 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); console.time("fib"); fib(42); console.timeEnd("fib");
-
函数式青年
const timed = fn => (...args) => { console.time(fn.name); const result = fn(...args); console.timeEnd(fn.name); return result; }; const fib = n => n <= 1 ? 1 : fib(n - 1) + fib(n - 2); timed(fib)(42);
优化性能
-
普通青年
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) => { console.time(fn.name); const result = fn(...args); console.timeEnd(fn.name); return result; }; timed(fib)(42);
-
函数式青年
const memoize = fn => { const memory = {}; return arg => { if (memory[arg]) return memory[arg]; else { memory[arg] = fn(arg); return memory[arg]; } }; }; const fib = memoize(n => n <= 1 ? 1 : fib(n - 1) + fib(n - 2) ); const timed = fn => (...args) => { console.time(fn.name); const result = fn(...args); console.timeEnd(fn.name); 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(() => {
// ...
});
debounce
场景:
输入框 change 事件触发向后台查询。
为消除不必要查询,用户连续输入时不触发查询,
当 200ms 内没有新的输入时,才向后台查询。
const debounce = (fn, ms = 200) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(
fn.bind(null, ...args),
ms
);
};
};
更多实际场景
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);
}
}
把出错重试的逻辑提取到高阶函数中:
const tryUntilSucceeded =
fn =>
async (...args) => {
while (true) {
try {
return await fn(...args);
} catch (err) {
console.log(err);
}
}
};
const enhancedGet = tryUntilSucceeded(get);
const enhancedPost = tryUntilSucceeded(post);
const resGet = await enhancedGet(path);
const resPost = await enhancedPost(path);
小结
两个代码块一样,把这个代码块提取出来,封成一个函数,减少代码重复,这个技巧大家都知道;两段代码流程一样,用高阶函数把公共流程提取出来,减少代码重复,这个技巧知道的人就不多了。可以类比 react 高阶组件,道理是一样的。