Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Genzhen opened this issue Jan 14, 2021 · 2 comments
Open
Labels
JavaScript teach_tag

Comments

@Genzhen
Copy link
Collaborator

Genzhen commented Jan 14, 2021

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

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁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 参数是函数,返回也是一个函数
  • 除了第一个函数接受参数,其它函数接受的都是上一个函数的返回值,所以初始函数的参数是多元的,而其它函数的接受值是一元的。
  • 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);
  };
}
  • lodash.js 库的实现
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,还多了一层函数的校验,而且接收的是数组,不是参数序列。

@Genzhen Genzhen added the JavaScript teach_tag label Jan 14, 2021
@lightzhu
Copy link

lightzhu commented Jan 15, 2021

递归实现的有问题吧,无法执行
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
Copy link
Collaborator Author

Genzhen commented Jan 15, 2021

递归实现的有问题吧,无法执行
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"));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript teach_tag
Projects
None yet
Development

No branches or pull requests

2 participants