欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

高阶函数 - Higher Order Function

程序员文章站 2022-04-15 14:51:46
一个函数 如果输入参数包含函数 或 返回值是函数,就称为高阶函数。 这篇文章介绍高阶函数的一个子集:输入 fn,输出 fn'。 ......

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

这篇文章介绍高阶函数的一个子集:输入 fn,输出 fn'
fnfn'功能是否一致【即相同输入是否始终对应相同输出】,把这类高阶函数的作用分为两类:

  1. 包装函数:功能一致
  2. 修改函数:功能不一致

包装函数

从斐波那契数列开始。

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 只关心数列的计算逻辑
    记录时间、性能优化的事情交给 timedmemorized 处理
    这样 fib 逻辑简单,不容易出错
  • 代码可复用,减少了代码重复
    像上面的timed, memorized......tryuntilsucceeded都可以提取到公共库,供别的地方使用