JS之函数式编程compose和pipe

1,807 阅读3分钟

前言

关于JS函数式编程,之前已经讨论了其中的柯里化,另外还有一个偏函数,跟柯里化非常相似

柯里化和偏函数的区别

偏函数固定了部分参数,生成另外一个参数更少的方法,比如bind()

今天再学习以下composepipe

compose和pipe

应用场景:有一些公用函数,现在需要执行一系列操作,就需要不停的调用这些公用函数,所以就会出现一个接一个的调用,如果有一个函数可以把这些需要调用的函数按照顺序链接起来,直接调用这个函数不就可以了。

	// 公共函数,顺序由上至下
	let greeting = (firstname,lastname) => "h1,"+firstname+lastname;
    let toUpper = str => str.toUpperCase();
    let trimSpace = str => str.replace(/\s+/g,'');

现在用一个函数把这三个函数包装起来,这个包装函数可以是compose或者pipe

pipe和compose的区别
pipe函数包装函数是根据参数顺序包装pipe([greeting,toUpper,trimSpace]),而compose是根据参数倒序包装compose([trimSpace,toUpper,greeting])

pipe 的实现

	let pipe = function (funcs) {
        let len = funcs.length;
        // 判断参数是否都是函数
        let index = len;
        while (index--) {
            if(typeof funcs[index] !== 'function') {
                throw new TypeError('Excepted  a function');
            }
        }
        return function(...args) {
            let index = 0;
            // 如果没有包装任何一个函数,则返回调用包装函数的第一个参数
            let result = len ? funcs[index].apply(this,args):args[0];
            // 循环绑定
            while (++index<len) {
                result = funcs[index].call(this,result);
            }
            return result;
        }
    };
    // compose的实现
    let compose = function(funcs) {
        return pipe(funcs.reverse());
    };
    let fn = flow([greeting,toUpper,cutTrim]);
    console.log(fn('y i n g','m u'));

上面是用循环实现的,下面用递归的思想实现

    let pipe = function (funcs) {
        let len = funcs.length;
        let index = 0;
        let result;
        return function f1(...args) {
            // 第一次args是传进来的参数,之后args==result
            result = funcs[index].apply(this, args);
            if (index >= len - 1) {
                // 重置下标为0
                index = 0;
                return result;
            }
            index++;
            return f1.call(null, result);
        }
    };
    // compose的实现
    let compose = function (args) {
        return pipe(args.reverse());
    };
    let fn1 = compose([trimSpace, toUpper, greeting]);
    console.log(fn1('h u a', 'd  a o'));

reduce实现

另外,还可以用reduce来实现

Array.prototype.reduce

reducemap很相似,但是数组进行map操作的时候会把每一个元素都被回调函数‘作用’一次,然后返回一个新的数组,而数据执行reduce时,每个元素被回调函数‘作用’一次后都把返回的结果传递给下一个元素的回调函数(即回调函数的第一个参数),然后这个结果跟下一个元素继续‘作用’

基本语法

    // 接收两个参数,第一个执行函数,第二个初始值(可选)
    arr.reduce(callback[, initialValue]);
    // callback共接收4个参数:
    // 1. accumulator 上一次回调的返回值
    // 2. currentValue 当前正在处理的元素
    // 3. index (可选) 当前元素的索引
    // 4. array (可选) 当前调用reduce的数组

示例

    // 数组求和,初始值为10
    let arr = [1,2,3,4,5];
    let sum = arr.reduce((accumulator,currentValue) => accumulator+currentValue,10);
    console.log(sum)      // 25

compose和pipe实现剖析

假如要把函数a,b,c,d串联起来,其实实现的最终结果就是a(b(c(d(args)))),用reduce实现就是把函数a,b,c,d当做一个数组,然后进行遍历,初始值为函数d的参数,第一次回调返回d(args),然后把这个结果作为第二次回调的参数作用于c,返回c(d(args)),以此类推...

代码实现

    let pipe = (...funcs) => (...args) => funcs.reduce((arg, fuc, index) => index == 0 ? fuc.apply(this, arg) : fuc.call(this, arg), args);
    let fn = pipe(greeting, toUpper, cutTrim);
    console.log(fn('h u a', 'd  a o'))