1、compose函数
用过redux或者对Koa、Express等有点了解的同学应该都听过中间件这个名词,它可以让我们通过插件的形式对原本的代码执行流程进行安全的包装。其中最核心的思想就是组合函数compose,如下:
// 代码摘自redux
const compose = function(...fns){
// 没有传入函数参数,就返回一个默认函数
if(fns.length == 0){
return (args) => args
}
// 只传入一个函数时,直接执行
if(fns.length == 1){
return fns[0]
}
// 组合函数
return fns.reduce((a,b) => (...args) => a(b(...args)))
}
不到10行的代码,却是整个中间件模式的精髓。可能有的同学会有这样的疑惑,上面的每行代码我都能看懂,可是这个函数到底是想表达个什么意思呢?
别着急,下面我来手摸手(限女生)带你去理解它。
2、compose实现原理
show me the code!!
var f1 = (arg1) => {
console.log(`fn1: ${arg1}`)
return 'hello1'
}
var f2 = (arg2) => {
console.log(`fn2: ${arg2}`)
return 'hello2'
}
var f3 = (arg3) => {
console.log(`fn3: ${arg3}`)
return 'hello3'
}
var f4 = (arg4) => {
console.log(`fn4: ${arg4}`)
return 'hello4'
}
const composed = compose(f4,f3,f2,f1)
composed('hello')
// 为方便分析,这里分开写的。等价于compose(f4,f3,f2,f1)('hello')
执行结果

接下来,让我们的大脑化身v8引擎,一步步的执行上码这段代码,看看会发生什么。
首先,我们快速的申明变量、赋值等操作。完成这些准备工作后,来到compose(f4,f3,f2,f1)阶段。
compose(f4,f3,f2,f1)执行过程解析
这个会返回什么呢?查看上面的compose函数实现,发现返回的是这行代码[f1, f2,f3,f4].reduce((a,b) => (...args) => a(b(...args)))的执行结果。那这行代码具体进行了什么操作呢?可能很多同学跟我刚开始一样云里雾里,别着急,我们接下来就具体分析下:
reduce函数复习:
arr.reduce(callback,[initialValue])
reduce为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素。
callback接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用reduce 的数组。
initialValue作为第一次调用 callback 的第一个参数
- 第一次迭代:
a指向f4,b指向f3- 返回一个函数(为方便行文,记做
A)(...args) => f4(f3(...args))
- 第二次迭代
a指向第一次迭代的返回值A,b指向f2- 返回一个函数(记做
B)(...args) => f4(f3(f2(...args)))
- 第三次迭代
a指向第二次迭代的返回值B,b指向f1- 返回一个函数(记做
C)(...args) => f4(f3(f2(f1(...args)))) - 迭代完成,返回函数
C给外部,并赋值给composed
composed('hello')执行过程解析
紧接着,执行composed('hello'), 即f4(f3(f2(f1('hello')))),这里又会发生什么事情呢?
- 首先
f4入栈,f4开始执行,执行时发现参数是一个函数的调用,那么就会执行该函数f3,而不会直接执行f4的函数体( console.log(fn4: ${arg4});return ‘hello4’)(f3,f2,f1同理) f3入栈f2入栈f1入栈- 发现
f1没有继续调用其他函数,开始执行f1函数体,打印fn1: hello;f1完毕,出栈,返回'hello1'给f2 - 开始执
f2函数体,打印fn2: hello2;f2执行完毕,出栈,返回'hello2'给f3 - 开始执
f3函数体,打印fn3: hello3;f3执行完毕,出栈,返回'hello3'给f4 - 开始执
f4函数体,打印fn4: hello4;f4执行完毕,出栈,返回'hello4'给最外层

(怎么样,是不是和回调地狱有点像 ๑乛◡乛๑)
3、中间件的实现
1.1 需求分析
上面的搞懂之后,接下来思考一下怎样实现这种形式,就是在进入下一个函数栈之前执行一些逻辑,然后在函数栈弹出后再执行一些逻辑。

我们先确定下实现的思路,要在f4执行的过程时候中间穿插着f3函数的执行,就是说f4要拥有对f3的控制权(上面的那个例子是进入f4之后直接执行了f3,没法控制它),那么怎么去实现把内层函数的执行控制权交给外面呢?答案就是:把内层函数包裹在一个新的函数里面,然后再返回就可以了。这样内层函数的执行权就层层向外的传递到了最外层函数。即 f4控制f3,f3控制f2…
// 我们质询要改动f1,f2,f3,f4
var f1 = (next) => {
return function(action){
console.log(`f1开始`)
const res = next(action + "_1")
console.log(`f1结束`)
return res
}
}
// f2、f3、f4 和上面类似
1.2 原理分析
我们先回到最开始的那个函数 f4(f3(f2(f1('hello'))),可以看到f1的返回值会成为f2的参数(f2,f3,f4同理),这样我们可以画出下面这张图

// 上面代码等价于:
(next) => {
return function(action) {
console.log(`f4开始`)
const res = function(action) {
console.log(`f3开始`)
const res = function(action) {
console.log(`f2开始`)
const res = function(action) {
console.log(`f1开始`)
const res = next(action)
console.log(`f1结束`)
return res
}
console.log(`f2结束`) return res
}
console.log(`f3结束`) return res
}
console.log(`f4结束`) return res
}
}
这样就实现了上面的需求:f1在f2的逻辑里执行,f2在f3的逻辑里执行…。到此,一个简单的中间件就已经完成了。
注意,最后由于f4返回的是一个函数,所以还得再调用一次:compose(f4,f3,f2,f1)(next)('action')
其中next是传递给f1的初始参数,字符串action是传给f1返回的函数的初始参数
1.3最终完整代码
var compose = function(...fns){
// 没有传入函数参数,就返回一个默认函数
if(fns.length == 0){
return (...args) => args
}
// 只传入一个函数时,直接执行
if(fns.length == 1){
return fns[0]
}
// 组合函数
return fns.reduce((a,b) => (...args) => a(b(...args)))
}
var f1 = (next) => {
return function(action){
console.log(`f1开始`)
const res = next(action + "_1")
console.log(`f1结束`)
return res
}
}
var f2 = (next) => {
return function(action){
console.log(`f2开始`)
const res = next(action + '_2')
console.log(`f2结束`)
return res
}
}
var f3 = (next) => {
return function(action){
console.log(`f3开始`)
const res = next(action + "_3")
console.log(`f3结束`)
return res
}
}
var f4 = (next) => {
return function(action){
console.log(`f4开始`)
const res = next(action + "_4")
console.log(`f4结束`)
return res
}
}
var reducer = (action) => {
return 'data from reducer' + ' ' + action
}
var next= (action) => {
console.log('开始dispatch')
return reducer(action)
}
compose(f4,f3,f2,f1)(next)('action')
1.4执行结果


本文围绕compose函数展开,介绍了其在中间件模式中的核心作用。详细分析了compose函数的实现原理,通过对reduce函数的复习和迭代过程的解析,展示了代码执行流程。还探讨了中间件的实现,包括需求分析、原理分析,给出了最终完整代码及执行结果。
195

被折叠的 条评论
为什么被折叠?



