高階函數 - Higher Order Function

一個函數 若是輸入參數包含函數 或 返回值是函數,就稱爲高階函數。javascript

這篇文章介紹高階函數的一個子集:輸入 fn,輸出 fn'
fnfn'功能是否一致【即相同輸入是否始終對應相同輸出】,把這類高階函數的做用分爲兩類:java

  1. 包裝函數:功能一致
  2. 修改函數:功能不一致

包裝函數

從斐波那契數列開始。react

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");
  • 函數式青年session

    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);

優化性能

  • 普通青年async

    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 超時錯誤,只但願彈一個從新登陸提示框。post

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);

小結

兩個代碼塊同樣,把這個代碼塊提取出來,封成一個函數,減小代碼重複,這個技巧你們都知道;

兩段代碼流程同樣,用高階函數把公共的流程提取出來,減小代碼重複,這個技巧知道的人就很少了。能夠類比react的高階組件,道理是同樣的。

相關文章
相關標籤/搜索