一個函數 如果輸入參數包含函數 或 返回值是函數,就稱為高階函數。 這篇文章介紹高階函數的一個子集:輸入 fn,輸出 fn'。 ...
一個函數 如果輸入參數包含函數 或 返回值是函數,就稱為高階函數。
這篇文章介紹高階函數的一個子集:輸入
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 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) => { 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(...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);
小結
兩個代碼塊一樣,把這個代碼塊提取出來,封成一個函數,減少代碼重覆,這個技巧大家都知道;
兩段代碼流程一樣,用高階函數把公共的流程提取出來,減少代碼重覆,這個技巧知道的人就不多了。