lgwebdream / FE-Interview

🔥🔥🔥 前端面试,独有前端面试题详解,前端面试刷题必备,1000+前端面试真题,Html、Css、JavaScript、Vue、React、Node、TypeScript、Webpack、算法、网络与安全、浏览器
https://lgwebdream.github.io/FE-Interview/
Other
6.82k stars 896 forks source link

Day220:了解函数式编程中的 compose 吗?动手实现一下? #1039

Open Genzhen opened 3 years ago

Genzhen commented 3 years ago

每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案 欢迎大家在下方发表自己的优质见解 二维码加载失败可点击 小程序二维码

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。


compose

compose 是函数式编程中一个非常重要的函数,compose 的函数作用就是组合函数的,将函数串联起来执行。将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。

2.1 简单例子

比如有这样的需求,输入一个名字 yideng,然后全部变成大写,打印输出 HELLO YIDENG,我们可以考虑用函数组合的方法来解决这个问题,需要两个函数 greeting、toUpper

const greeting = (name) => `hello ${name}`;
const toUpper = (str) => str.toUpperCase();
const fn = compose(toUpper, greeting);
console.log(fn("yideng"));
// HELLO YIDENG

这就是 compose 的大致使用,主要有以下几点:

知道这几点之后,上边的 compose 执行过程就很容易分析出来了,执行 fn('yideng')的时候,初始函数为 greeting,执行结果作为参数传递给 toUpper,再执行 toUpper,得出最后的结果。

compose 的好处就是如果再想加一个处理函数,不需要修改 fn,再执行一个 compose 就可以了。比如再加一个 trim。

const trim = (str) => str.trim();
const newFn = compose(trim, fn);
console.log(newFn("yideng"));

2.2 参考实现

const compose = function (...funcs) {
        let len = funcs.length,
          count = len - 1,
          result = null;
        // 首先compse 返回的是一个函数
        return function fn(...args) {
          // 函数体里就是不断执行args函数,将上一个函数的执行结果作为下一个执行函数的输入参数,需要一个count来记录args函数列表的执行情况
          result = funcs[count].apply(this, args);
          // 递归退出条件
          if (count <= 0) {
            count = len - 1;
            return result;
          } else {
            count--;
            return fn.call(null, result);
          }
        };
      };
      // 测试
      const greeting = (name) => `hello ${name}`;
      const toUpper = (str) => str.toUpperCase();
      const fn = compose(toUpper, greeting);
      console.log(fn("yideng"));
function compose(...fns) {
  let isFirst = true;
  return (...args) => {
    return fns.reduceRight((result, fn) => {
      if (!isFirst) return fn(result);
      isFirst = false;
      return fn(...result);
    }, args);
  };
}
var flow = function (funcs) {
  var length = funcs.length;
  var index = length;
  while (index--) {
    if (typeof funcs[index] !== "function") {
      throw new TypeError("Expected a function");
    }
  }
  return function (...args) {
    var index = 0;
    var result = length ? funcs[index].apply(this, args) : args[0];
    while (++index < length) {
      result = funcs[index].call(this, result);
    }
    return result;
  };
};
var flowRight = function (funcs) {
  return flow(funcs.reverse());
};

const greeting = (name) => `hello ${name}`;
const toUpper = (str) => str.toUpperCase();
const fn = flowRight([toUpper, greeting]);
console.log(fn("yideng"));

可以看出,lodash 的本来实现是从左到右实现的,但也提供了从右到左的 flowRight,还多了一层函数的校验,而且接收的是数组,不是参数序列。

lightzhu commented 3 years ago

递归实现的有问题吧,无法执行 const compose = function (...fns) { let len = fns.length let result = null let index = len - 1 return function fn(args) { result = fns[index].call(this, args) if (index > 0) { index-- fn.call(null, result) } return result } } const greeting = (name) => hello ${name}; const toUpper = (str) => str.toUpperCase(); const fun = compose(toUpper, greeting); console.log(fun("yideng"));

Genzhen commented 3 years ago

递归实现的有问题吧,无法执行 const compose = function (...fns) { let len = fns.length let result = null let index = len - 1 return function fn(args) { result = fns[index].call(this, args) if (index > 0) { index-- fn.call(null, result) } return result } } const greeting = (name) => hello ${name}; const toUpper = (str) => str.toUpperCase(); const fun = compose(toUpper, greeting); console.log(fun("yideng"));

@lightzhu 感谢指出,已修改

const compose = function (...funcs) {
        let len = funcs.length,
          count = len - 1,
          result = null;
        // 首先compse 返回的是一个函数
        return function fn(...args) {
          // 函数体里就是不断执行args函数,将上一个函数的执行结果作为下一个执行函数的输入参数,需要一个count来记录args函数列表的执行情况
          result = funcs[count].apply(this, args);
          // 递归退出条件
          if (count <= 0) {
            count = len - 1;
            return result;
          } else {
            count--;
            return fn.call(null, result);
          }
        };
      };
      // 测试
      const greeting = (name) => `hello ${name}`;
      const toUpper = (str) => str.toUpperCase();
      const fn = compose(toUpper, greeting);
      console.log(fn("yideng"));