JS学习笔记——高级编程中compose函数的介绍和基本实现

1、前言

在之前探讨redux的中间件的时候,applyMiddleware源码中有遇到过compose()函数,当时不太明白起作用,所以就上网好好查了一下,做了个总结。

2、普通函数

在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。

例1:
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
console.log(fn3(fn2(fn1(1))))   ===>  100

我们想输出的是一个多层函数嵌套的运行结果,即把前一个函数的运行结果赋值给后一个函数。但是如果需要嵌套多层函数,那这种类似于f(g(h(x)))的写法可读性太差,我们考虑能不能写成(f, g, h)(x)这种简单直观的形式,于是compose()函数就正好帮助我们实现。

3、compose函数

3.1、compose的概念

先介绍一下compose()函数的概念

  • 概念:将需要嵌套执行的函数扁平化处理嵌套执行指的是,一个函数的返回值将作为另一个函数的参数
  • 作用:实现函数式编程中的 pointfree 风格(无参数),使我们专注于【转换】而不是【数据】
  • 实现:接收多个函数作为参数,从右到左,一个函数的输入为另一个函数的输出
  • 意义:编程更精练、算法更清晰、无参数干扰
  • 威力:【任意组合】
  • 缺点:不能直观的看到参数
3.2、compose的实现

首先,我们先看一下参数,funcs就是需要给出不确定个数的具体函数

function compose(...funcs) {
    //=>funcs:传递的函数集合
}

接着,我们看到compose函数执行后跟个(),说明外层函数执行完会再执行一个函数,即函数执行完会返回一个新函数,而且也会给出第一次调用函数时的参数

function compose(...funcs) {
    //=>funcs:传递的函数集合
    return function proxy(...args) { 
      //=>args:第一次调用函数传递的参数集合

    }    
}

紧接着,我们需要判断给出的函数集合的个数

  • 如果没有给函数,我们只需将后一个的参数返回;
  • 如果只给出一个函数,我们只需把后一个的参数赋给这个函数去执行即可:
function compose(...funcs) {
    //=>funcs:传递的函数集合
    return function proxy(...args) {
        //=>args:第一次调用函数传递的参数集合
        
        let len = funcs.length;
        if (len === 0) {
            //=>一个函数都不需要执行,直接返回参数args
            return args;
        }
        if (len === 1) {
            //=>只需要执行第一个函数,把函数执行,把其结果返回即可
            return funcs[0](...args);
        }

    };
}
  • 如果给出的函数参数集合是两个及以上,那就是把前一个函数的执行结果赋给后一个函数。说到这,应该会想到一个满足这个需求的数组方法——reduce:
 function compose(...funcs) {
    //=>funcs:传递的函数集合
    return function proxy(...args) {
        //=>args:第一次调用函数传递的参数集合
        let len = funcs.length;
        if (len === 0) {
            //=>一个函数都不需要执行,直接返回参数args
            return args;
        }
        if (len === 1) {
            //=>只需要执行第一个函数,把函数执行,把其结果返回即可
            return funcs[0](...args);
        }
        return funcs.reduce((x, y) => {
        	//TODO
        });
    };
}

最后,我们需要注意的是,第一次执行的时候,参数x是个函数之后再执行的时候x是个函数执行的结果,所以需要进行判断:

function compose(...funcs) {
    //=>funcs:传递的函数集合
    return function proxy(...args) {
        //=>args:第一次调用函数传递的参数集合
        let len = funcs.length;
        if (len === 0) {
            //=>一个函数都不需要执行,直接返回ARGS
            return args;
        }
        if (len === 1) {
            //=>只需要执行一个函数,把函数执行,把其结果返回即可
            return funcs[0](...args);
        }
        return funcs.reduce((x, y) => {
            return typeof x === "function" ? y(x(...args)) : y(x)
        });
    };
}

这样,我们就完成了compose函数,我们将开始普通函数的案例改为compose函数:

unction compose(...funcs) {
    //=>funcs:传递的函数集合
    console.log('---funcs---',funcs)
    return function proxy(...args) {
        //=>args:第一次调用函数传递的参数集合
        let len = funcs.length;
        if (len === 0) {
            //=>一个函数都不需要执行,直接返回args
            return args;
        }
        if (len === 1) {
            //=>只需要执行一个函数,把函数执行,把其结果返回即可
            return funcs[0](...args);
        }
        return funcs.reduce((x, y) => {
            console.log('--x--',x)
            console.log('--y--',y)
            return typeof x === "function" ? y(x(...args)) : y(x)
        });
    };
}
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);

在这里插入图片描述

  • 我们看出,第一次执行时,x是一个函数;第二次执行时,x是一个常数
  • 但是我们发现执行的结果是从左到右,即fn3 ——> fn2 ——> fn1,并不是我们期望的从右向左。所以我们可以使用reduceRight()或者reverse()帮助我们实现

我们将reduce改为reduceRightreverse()

function compose(...funcs) {
    //=>funcs:传递的函数集合
    console.log('---funcs---',funcs)
    return function proxy(...args) {
        //=>args:第一次调用函数传递的参数集合
        let len = funcs.length;
        if (len === 0) {
            //=>一个函数都不需要执行,直接返回args
            return args;
        }
        if (len === 1) {
            //=>只需要执行一个函数,把函数执行,把其结果返回即可
            return funcs[0](...args);
        }
        //方式一
        return funcs.reduceRight((x, y) => {
            console.log('--x--',x)
            console.log('--y--',y)
            return typeof x === "function" ? y(x(...args)) : y(x)
        });
        //方式二
         return funcs.reverse().reduce((x, y) => {
            console.log('--x--',x)
            console.log('--y--',y)
            return typeof x === "function" ? y(x(...args)) : y(x)
        });
    };
}
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);

在这里插入图片描述

  • 这次,我们就实现了最初普通函数时这种(fn3(fn2(fn1(1))))嵌套逻辑,即利用compose函数compose(fn3, fn2, fn1)(1)

4、redux源码中的compose

在redux源码中compose有这样一种写法:

function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }
 
    if (funcs.length === 1) {
        return funcs[0]
    }
 
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
} 

这种写法更简便,不用对第一个参数函数进行判断,与我们自己写compose函数的区别就是:x(y)还是y(x),这点我们要注意

同样,我们用刚才的案例测试redux源码中的compose函数:

function compose(...funcs) {
    if (funcs.length === 0) {
        return arg => arg
    }
 
    if (funcs.length === 1) {
        return funcs[0]
    }
  	console.log('---funcs---',funcs)
    return funcs.reduce((a, b) => (
        console.log('--a--',a),
        console.log('--b--',b),
        (...args) => a(b(...args))
    ))
} 
const fn1 = (x) => x + 10;
const fn2 = (x) => x * 10;
const fn3 = (x) => x - 10;
let result = compose(fn3, fn2, fn1)(1);
console.log('--result--',result);

在这里插入图片描述


本文参考博客:

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值