上篇文章中,我们提到了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
文章采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接。