JavaScript基础:手写实现函数组合(compose)

在这里插入图片描述

一、概念

compose 是函数式编程中很重要的函数之一, 因为其巧妙的设计而被广泛使用。compose 函数的作用就是组合函数的,将函数串联起来执行,将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。

组合的概念是非常直观的,并不是函数式编程独有的,在我们生活中或者前端开发中处处可见。

比如我们现在流行的 SPA (单页面应用),都会有组件的概念,为什么要有组件的概念呢,因为它的目的就是想让你把一些通用的功能或者元素组合抽象成可重用的组件,就算不通用,你在构建一个复杂页面的时候也可以拆分成一个个具有简单功能的组件,然后再组合成你满足各种需求的页面。

其实函数组合也是类似,函数组合就是一种将已被分解的简单任务组合成复杂任务的过程。

按照我们对组合的理解,现假定有compose函数可以实现如下功能:

function compose(...fns){
    //忽略
}
// compose(f,g)(x) === f(g(x))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m,n)(x) === f(g(m(n(x))))
//···

我们可以看到compose函数,会接收若干个函数作为参数,每个函数执行后的输出作为下一个函数的输出,直至最后一个函数的输出作为最终的结果。

二、应用

在创建并完善我们自己的compose函数前,我们先来学习一下如何应用compose函数。

假定有这样一个需求:对一个给定的数字四舍五入求值,数字为字符型。

常规实现:

let n = '3.56';
let data = parseFloat(n);
let result = Math.round(data); 
// 最终结果 result为4 

在这段代码中,可以看到parseFloat函数的输出作为输入传递给Math.round函数以获得最终结果,这是compose函数能够解决的典型问题。

compose函数改写:

let n = '3.56';
let number = compose(Math.round,parseFloat);
let result = number(n); 
// 最终结果 result为4 

这段代码的核心是通过composeparseFloatMath.round组合到一起,返回一个新函数number

这个组合的过程就是函数式组合!我们将两个函数组合在一起以便能及时的构造出一个新函数!

再举一个例子,假设我们有两个函数:

let splitIntoSpaces = (str) => return str.split(' ');
let count = (array) => return array.length;

现希望构建一个新函数以便计算一个字符串中单词的数量,可以很容易的实现:

let countWords = compose(count,splitIntoSpaces);

调用一下:

let result = countWords('hello your reading about composition'); 
// 最终结果 result为5

三、实现

1.实现组合

先回看compose函数到底做了什么事:

// compose(f,g)(x) === f(g(x))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m)(x) === f(g(m(x)))
// compose(f,g,m,n)(x) === f(g(m(n(x))))
//···

概括来说,就是接收若干个函数作为参数,返回一个新函数。新函数执行时,按照由右向左的顺序依次执行传入compose中的函数,每个函数的执行结果作为为下一个函数的输入,直至最后一个函数的输出作为最终的输出结果。

如果compose函数接收的函数数量是固定的,那么实现起来很简单也很好理解。

只接收两个参数:

function compose(f,g){
    return function(x){
        return f(g(x));
    }
}

只接收三个参数:

function compose(f,g,m){
    return function(x){
        return f(g(m(x)));
    }
}

上面的代码,没什么问题,但是我们要考虑的是compose接收的参数个数是不确定的,我们考虑用rest参数来接收:

function compose(...fns){
    return function(x){
        //···
    }
}

现在compose接收的参数fns是一个数组,那么现在思考的问题变成了,如何将数组中的函数从右至左依次执行。

我们选择数组的reduce函数来实现:

function compose(...fns) {
  return function(x){
    return fns.reverse().reduce(function(arg, fn, index){
      return fn(arg);
    }, x);
  }
}

我们选择数组的reduceRight函数来实现:

function compose(...fns){
    return function(x){
        return fns.reduceRight(function(arg,fn){
            return fn(arg);
        },x)
    }
}

补充:

1.reduce()方法

reduce()方法接收一个函数callbackfn作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。

语法:

array.reduce(callbackfn,[initialValue]) 

reduce()方法接收callbackfn函数,而这个函数包含四个参数:

function callbackfn(preValue,curValue,index,array){} 
  • preValue: 上一次调用回调返回的值,或者是提供的初始值(initialValue
  • curValue: 数组中当前被处理的数组项
  • index: 当前数组项在数组中的索引值
  • array: 调用 reduce()方法的数组

initialValue作为第一次调用 callbackfn函数的第一个参数。

2.reduceRight()方法

reduceRight()方法的功能和reduce()功能是一样的,不同的是reduceRight()从数组的末尾向前将数组中的数组项做累加。

reduceRight()首次调用回调函数callbackfn时,prevValuecurValue 可以是两个值之一。如果调用 reduceRight() 时提供了 initialValue 参数,则 prevValue 等于 initialValuecurValue 等于数组中的最后一个值。如果没有提供 initialValue 参数,则 prevValue 等于数组最后一个值, curValue 等于数组中倒数第二个值。

2.实现管道

compose的数据流是从右至左的,因为最右侧的函数首先执行,最左侧的函数最后执行!

但有些人喜欢从左至右的执行方式,即最左侧的函数首先执行,最右侧的函数最后执行!

从左至右处理数据流的过程称之为管道(pipeline)!

管道(pipeline)的实现同compose的实现方式很类似,因为二者的区别仅仅是数据流的方向不同而已。

对比compose函数的实现,仅需将reduceRight替换为reduce即可:

function pipe(...fns){
    return function(x){
        return fns.reduce(function(arg,fn){
            return fn(arg);
        },x)
    }
}

组合相比,有些人更喜欢管道。这只是个人偏好,与底层实现无关。重点是pipecompose做同样的是事情,只是数据流放行不同而已!我们可以在代码中使用pipecompose,但不要同时使用,因为这会在团队成员中引起混淆。如果要使用,请坚持只用一种组合的风格。

四、总结

组合的方式就是抽象单一功能的函数,然后将这些函数进行再组合,从而达到实现复杂功能的目的,这样不仅使代码逻辑更加清晰,也给维护带来巨大的方便。

(求点赞关注)参考连接:https://juejin.cn/post/6844903910834962446#heading-5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值