小白带你了解 “洋葱模型”

747 阅读4分钟

前言

无论是nodeJS框架koaegg,还是小程序云开发框架tcb-router,在处理中间件时都采用了洋葱模型

img

在这里肯定还有很多朋友不知道什么是中间件,我这引用一段很通俗的话:

中间件相当于太监,内部的核心业务相当于皇上,大臣在上奏时,不会直接传输给皇上,要通过太监呈递、安全处理了才递给皇上。

图中请求会通过中间件层层递进到内部,再从内部到外部传输,有点类似于前端的事件捕获,先从外到内,在从内到外的机制。

看到这里你可能还不太懂怎么去实现这样一个模型?这个模型又有什么样的用处?那么你就容我细细道来,以小白的视角窥探事物的本质。

什么是洋葱模型

在讲什么是洋葱模型之前,我们先来一道题。

function fn1(next) {
    console.log(1)
    next()
    console.log(6)
}
function fn2(next) {
    console.log(2)
    next()
    console.log(5)
}
function fn3(next) {
    console.log(3)
    console.log(4)
}

/**
* 设计一个compose函数, 在执行这个函数的时候,去调用传入的函数参数,并且输出  1、2、3、4、5、6
*/
compose([fn1,fn2,fn3])
function compose(fnList){}
流程图分析

androidhierchy.png

由图不难看出:

  • next指向的是下一个函数的地址,执行next函数,就是在函数内部执行下一个索引的函数
  • fn3函数因为没有下一个函数了, next就没有指向,所有就没有next
  • 每个函数被next分成了三份
  • 以上实现模式的算法,使用递归调用,又借助了栈的思想(先进后出)
代码实现分析

代码中最复杂的就是next函数的指向,只要保证 next指向下一个函数,那就很容易实现洋葱模型

//compose([fn1, fn2, fn3])

function compose(fnList) {
    return dispatch(0)

    /**
    * 执行fnList中的函数,生成next参数,并且当作形参传入要执行的函数
    * @param {number} i 当前要执行那个函数的索引
    */
    function dispatch(i) {
        let fn = fnList[i]   //要执行那个函数
        let nextFnI = i + 1   //下一个函数的索引
        let nextFn=dispatch.bind(null, nextFnI)  //dispatch传入nextFnI就是要返回 下一个函数的指向
        let next = nextFn  //生成next参数
        fn(next)   //执行当前函数,并且传入下一个函数的指向next
    }

}

因为为了更容易理解代码,删减了代码很多细节,但是compose函数的思路没有改变。

其中最核心的代码就是生成next并且传入到当前需要执行函数的参数

dispatch函数的目的就是执行函数

函数执行栈分析
入栈
  1. dispatch(0)就是执行fnList[0]函数,让fnList[0]入函数调用栈,nextfnList[1]函数的指向 ==》执行console.log(1)

img

  1. fnList[0]nextfnLis[1]函数)参数执行时,本质是在函数调用栈中入栈了一个fnLis[1]函数,所以要等fnLis[1]执行完毕才能继续执行fnList[0]函数,nextfnList[2]函数的指向 ==》执行console.log(2)

img

  1. fnList[1]nextfnLis[2]函数)参数执行时,本质是在函数调用栈中入栈了一个fnLis[2]函数,所有要等fnLis[2]执行完毕才能执行fnList[1]函数,next 无指向 ==》执行console.log(3)

​ 在函数调用栈中,后进先出,所以最后加入的fnLis[2]函数最先指向完毕

出栈
  1. fnList[2]执行完毕,从函数调用栈中出栈 ==》执行console.log(4)

img

  1. fnList[1]执行完毕,从函数调用栈中出栈 ==》执行console.log(5)

img

  1. fnList[2]执行完毕,从函数调用栈中出栈,栈为空,所有函数执行完毕, ==》执行console.log(6)

img

​ 从①~⑥步依次输出了1-6的数字,模型被实现

总结

对于洋葱模型大多数教程都在讲koa的底层实现,但是对于小白这些都不好去理解,所有本文把洋葱模型单独抽离出来,去除了koa的异步模式,以同步的视角看该模式。无论是同步还是异步,算法的本质都不会变的,只要记住在洋葱模型中参数next指向下一个函数,那么你就懂得这种模式了!!!

如果想更深入了解洋葱模型,可以去看大佬的深入讲解如何更好地理解中间件和洋葱模型