compose 方法
compose 函数定义
compose方法利用数组的 reduce 方法,将多个方法按照一定的顺序组合为一个方法,从而达到一种函数自动有序执行的效果。其中各个方法的执行的顺序取决于 reduce 方法如何应用。compose的简单实现如下:
// compose 接受多个函数作为入参 fun1 fun2
function compose(...funs){
const len = funs.length
// 处理两种特殊情况,函数数量 为 0 || 1
if(!len) return (...arg) => arg
if(len === 1) return funs[0]
// 以下组合方式 b 方法 会 先于 a 方法执行
return funs.reduce((a,b) => (...arg) => a(b(arg)))
// 以下组合方式 a 方法会先于 b 方法执行
// return funs.reduce((a,b) => (...arg) => b(a(arg)))
}
被组合函数(a,b)的形式
b 函数的返回值为普通的数据,作为a 函数的入参
function b(){ // 如返回值为普通的对象 return {...} } function a(arg){ // arg 数据应用 || 处理 return {...} }
按照以上形式的这种形式,a 函数若想应用b函数的返回值,最好预先知道b函数返回值的数据类型及字段细节,这会导致 a,b 方法之间的耦合严重。
b 返回一个函数,作为 a 函数的入参
function b(){ // 返回值为函数 return (...arg) => { //... } } function a(next){ // next 前置操作 // next() 可以在这里操作 next // next 后置操作 return (...arg) => { // next 前置操作 // next() 可以在这里操作 next // next 后置操作 } }
相比上一种形式,这种形式可以使得函数之间达到解偶效果,即函数 a 无需关心函数 b 的返回值,只要知道其返回值是一个函数,并在自己认为合适的时机调用即可,这样同时也对函数调用顺序控制,从而达到类似洋葱模型的执行效果。
applyMiddleware 方法
applyMiddleware函数形式
构造闭包,为外部定义的方法,提供内部指定的apis;如下示例
// 接受外部定义的方法数组 funs function applyMiddleware(...funs){ const apis = { dispatch: () => ({}) } // 遍历执行 外部定义的方法,构造一个闭包;并将返回值作为 结果返回 return funs.map(fun => fun(apis)) }
被操作的funs函数形式
为了构造一个闭包,fun 的返回值应该是一个使用了 apis 的函数。
function myMiddleware1(apis){ // 返回一个 方法,该方法中会调用 apis 中暴露的方法,形成一个闭包, return actions => { // apis 方法调用 const { dispatch } = apis dispatch(actions) } } const myMiddlewareWrappers = applyMiddleware(myMiddleware1) // myMiddlewareWrappers 为 [actions => { const { dispatch } = apis; dispatch(actions);}]
可能会有疑问:既然只是 apis 内的函数调用,为什么要这么复杂,借助闭包实现?调用者直接通过调用 apis 内部的函数不就能达到目的?
闭包的应用:
- 闭包可以使我们在函数外部控制函数内部的执行逻辑,如常见的防抖、截流函数
- 利用闭包可以实现函数柯里化,从而达到函数入参缓存的效果,进而达到一种单例的效果
为什么不直接调用 apis 内部的方法:
- apis 内部的方法并不是一开始就存在的,可能是动态生成的,通过闭包可以起到1.b 所提到的单例效果
- 通常我们并不是单纯的函数调用,往往附带着额外的操作,不应用闭包会导致以下效果
function fun1(arg){ console.log(arg) apis.dispatch(arg) } function fun2(arg){ arg.b = 100 apis.dispatch(arg) } // apis 方法的调用可能会随处可见,但是仔细观察,apis 方法的调用其实都是重复的代码 对比应用闭包的效果 // 没有重复的 apis 内部函数调用,构造的闭包函数会自动执行 apis 内部的方法 // apis 暴露了但是没有完全暴露 const results = applyMiddleware(myMiddleware1,myMiddleware1) results[0](100) results[1](200)
结合
如何结合 compose 与 applyMiddleware 两个方法,得到魔法效果呢?
首先根据以上定义的 myMiddleware 形式的方法进行考虑,直接将 applyMiddleware 得到的 方法列表进行 composeconst myMiddlewareWrappers = applyMiddleware(myMiddleware1, myMiddleware2) // 通过 compose 将 方法列表组合为一个方法 const result = compose(...myMiddlewareWrappers) // 此时 result 为何种形式呢? // 根据 compose 的 作用,不难想象 result 为一个函数 // actions => myMiddleware1(myMiddleware2(actions))
到这里,已经将 compose 与 applyMiddleware 结合起来了。
但是仔细观察 myMiddleware1、2 函数的形式,会发现一些问题,myMiddleware1、2方法返回值不是一个函数,经过开篇对compose的分析可以得到,这种形式会带来函数之间 耦合严重 的问题。因此需要对myMiddleware进行一步改造,如下function myMiddleware3(apis){ // 返回一个 方法,该方法中会调用 apis 中暴露的方法,形成一个闭包, // 同时为了 保证 compose 过程中 每个 函数的 入参仍然为 一个函数,需要将以下返回值再 // 进行一层封装 return next => actions => { // ... next(actions) // ... } }
魔法
但是我们还是不满足以上的结果,apis 内部函数的调用不在函数调用者手里,即,函数调用者只能传入一个 actions 参数,而无法控制apis 函数的具体执行时机。如何达到控制反转的效果?
actions 其实可以是一个方法,此时就可以通过传参的方式将 apis 再传递给 actions,从而将 apis 控制权交给真正的函数调用者。如下function myMiddleware2(apis){ // 返回一个 方法,该方法中会调用 apis 中暴露的方法,形成一个闭包, return next => actions => { // 控制反转 actions(apis) } } // 可以 结合myMiddleware2 与myMiddleware1 两种形式的函数实现兼容 // 即 调用者需要 控制 apis 时通过 传入函数形式的 actions 即可,否则传入 apis 中指定形式入参即可 function myMiddleware2(apis){ // 返回一个 方法,该方法中会调用 apis 中暴露的方法,形成一个闭包, return next => actions => { // 控制反转 if(typeof actions === 'function') return actions(apis); next(actions) } }