中间件的原理解析

6 篇文章 0 订阅

1、compose函数

用过redux或者对KoaExpress等有点了解的同学应该都听过中间件这个名词,它可以让我们通过插件的形式对原本的代码执行流程进行安全的包装。其中最核心的思想就是组合函数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 的第一个参数

  1. 第一次迭代:
    • a指向f4,b指向f3
    • 返回一个函数(为方便行文,记做A(...args) => f4(f3(...args))
  2. 第二次迭代
    • a指向第一次迭代的返回值A,b指向f2
    • 返回一个函数(记做B(...args) => f4(f3(f2(...args)))
  3. 第三次迭代
    • a指向第二次迭代的返回值B,b指向f1
    • 返回一个函数(记做C(...args) => f4(f3(f2(f1(...args))))
    • 迭代完成,返回函数C给外部,并赋值给composed

composed('hello')执行过程解析
紧接着,执行composed('hello'), 即f4(f3(f2(f1('hello')))),这里又会发生什么事情呢?

  1. 首先f4入栈,f4开始执行,执行时发现参数是一个函数的调用,那么就会执行该函数f3,而不会直接执行f4的函数体( console.log(fn4: ${arg4});return ‘hello4’)(f3,f2,f1同理)
  2. f3入栈
  3. f2入栈
  4. f1入栈
  5. 发现f1没有继续调用其他函数,开始执行f1函数体,打印 fn1: hellof1完毕,出栈,返回 'hello1'f2
  6. 开始执f2函数体,打印fn2: hello2f2执行完毕,出栈,返回 'hello2'f3
  7. 开始执f3函数体,打印fn3: hello3f3执行完毕,出栈,返回 'hello3'f4
  8. 开始执f4函数体,打印fn4: hello4f4执行完毕,出栈,返回'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
    }
}

这样就实现了上面的需求:f1f2的逻辑里执行,f2f3的逻辑里执行…。到此,一个简单的中间件就已经完成了。

注意,最后由于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执行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值