VueBloghyhero6

compose 实现的几种方案(笔记)

2021-06-12 / 2021-06-12 / 360次浏览

上篇文章中,我们提到了pipe ,这篇文章我们实现 compose,这个俩个的区别 只是调用顺序不同稍后我们会提到。

提前需要预备的知识点

解构赋值

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

Arguments 对象
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments

Function.prototype.call()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

Array.prototype.reverse()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse

ES6 的箭头函数
() => () => {} 这个二级回调函数
相当等价于
function() {
    return function () {}
}

ok,下面让我们直入主题。

函数式概念-这个古老的概念如今在前端领域“遍地开花”,(但是我就没怎么使用过。。。)函数式很多的思想都值得借鉴,其中一个细节: compose 因为其巧妙的设计而被广泛的运用。对于它的实现,从面向过程式到函数式实现。我们来看看什么是compose。

compose 其实和前面提到的pipe 一样,就是执行一连串不定长度的任务(方法),比如:

let funcs = [fn1, fn2, fn3, fn4]
let composeFunc = compose(...funcs)
执行:
composeFunc(args)
就相当于:
fn1(fn2(fn3(fn4(args))))
尝试总结一下 compose 方法的关键点:
compose 的参数是函数数组,返回的也是一个函数
compose 的参数是任意长度的,所有的参数都是函数,执行方向是自右向左的,因此初始函数一定放到参数的最右面
compose 执行后返回的参数可以接收参数,这个参数将作为初始函数的参数,所以初始函数的参数是多元的,初始函数的返回结果将作为下一个函数的参数,以此类推。因此除了初始函数之外,其他函数的接收值是一元的。

函数式编程

然后我们发现,实际上, compose 和 pipe 的差别只是在于调用顺序的不同:

既然跟我们先前实现的pipe 方法如出一辙,那么还有什么好深入分析的呢?请继续阅读,看看还能玩出来什么花样。

compose 最简单的实现是面向过程的:

const compose = function(...args) {
    let length = args.length
    let count = length - 1
    let result
    return function f1 (...arg1) {
        result = args[count].apply(this, arg1)
        if ( count <= 0 ) {
            count = length - 1
            return result
        }
        count --
        return f1.call(null, result)
    }
}

这段代码可能需要多看几眼了,我自己使用代码测试了一番。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>对compose函数进行测试</title>
</head>
<body>
<script>
    const compose = function(...args) {
        let length = args.length
        let count = length - 1
        let result
        // 解构出 ...arg1 是个什么东西
        return function f1 (...arg1) {
            console.log('...arg1-->',arg1)
            result = args[count].apply(this, arg1)
            if (count <= 0) {
                count = length - 1
                return result
            }
            count--
            return f1.call(null, result)
        }
    }
    var fn1 = function(name){
        return 'hi:'+name
    }
    var fn2 = function(statement){
        return statement.toUpperCase()+'!'
    }
    let funcs = [fn1, fn2]
    let composeFunc = compose(...funcs)
    console.log(composeFunc('gg'))
</script>
</body>
</html>

小伙伴们可以对上述代码进行试验测试

这里关键是用到了闭包,使用闭包变量存储结果 result 和 函数数组长度以及遍历索引,并利用递归思想,进行结果的累加计算
聪明的小伙伴,可以更快的想到利用上文所讲到的 reduce 方法,应该能更函数式地解决问题:

const reduceFunc = (f, g)=> (...arg) => g.call(this, f.apply(this, arg))
const compose = (...args) => args.reverse().reduce(reduceFunc, args.shift())

我们继续开阔思路,"既然涉及串联和流程控制",那么还可以使用 Promise 实现:

const compose = (...args) => {
    let init = args.pop()
    return (...arg) => 
    args.reverse().reduce((sequence, fuc) =>
        sequence.then(result => func.call(null, result))
        , Promise.resolve(init.apply(null, arg))                  
    )
}

这种写法就很函数式了,

大佬表示:
这种实现利用了 Promise 特性:首先通过 Promise.resolve(init,apply(null, arg)) 启动逻辑,启动一个 resolve值 为最后一个参数后的返回值,依次执行函数,因为promise.then() 仍然返回一个 Promise 类型值。所以 reduce完全可以按照Promise 实例执行下去。

发散思路

既然能够实现 Promise 实现,那么 generator 当然应该可以可以实现。 有兴趣的读者可以去查看 generator 方法,

http://es6.ruanyifeng.com/#docs/generator

JS基础,event