最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    正文概述 掘金(掘金安东尼)   2021-08-16   434

    ?嵌套毛毛虫

    我猜你一定见过这样的代码:

    if(condition1 === A1){
        if(condition2 === A2){
            ...
        }else if(condition2 === B2){
            ...
        }else if(condition2 === C2){
            ...
        }else{
            ...
        }
    }esle if(condition1 === B1){
        ...
        ...
        ...
    }else if(condition1 === C1){
        ...
        ...
        ...
    }else if(condition1 === D1){
        ...
        ...
        ...
    }else{
        ...
    }
    

    讲真,并不是说这个代码有多坏,但是每次看到的时候都会引起本瓜不适。

    感觉它就像是一只毛毛虫。。。

    为了形象的表达这一点,本瓜诚邀灵魂画师 守护安东尼 作示意图一张,salute!!( ̄︶ ̄)↗ 

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    * 图片来源:守护安东尼,未经允许,随意转载。

    这样写,总是会伴随着各种各样的逻辑判断、隐式输入、输出,还真不太敢动它,担心它直接“死”给你看!

    ?责任链竹节

    镜头转向【责任链】,它是 23 种设计模式之一,属于行为型模式,关注对象之间的交互、通信;

    参数输入到一个初始函数中,如果不满足当前函数条件,则传递到下一函数中进行处理,满足停止,不满足再传递,这样 one by one 向后进行,直至满足条件或传递结束。

    一个个元函数就像是一节节竹节,独立可拆卸、再任意组装;

    闲话少说,实现它的代码大致是这样的:

    function A1(condition1){
        chainA2.next(chainB2).next(chainC2);
        return condition1 === A1 ? chainA2.setParam(condition2) : 'doNext'
    }
    
    function B1(condition1){
        return condition1 === B1 ? ... : 'doNext'
    }
    
    function C1(condition1){
       return condition1 === C1 ? ... : 'doNext'
    }
    
    function D1(condition1){
       return condition1 === D1 ? ... : 'doNext'
    }
    
    ...
    
    function A2(condition2){
        return condition2 === A2 ? ... : 'doNext'
    }
    
    function B2(condition2){
       return condition2 === B2 ? ... : 'doNext'
    }
    
    function C2(condition2){
       return condition2 === C2 ? ... : 'doNext'
    }
    
    chainA1.next(chainB1).next(chainC1).next(chainD1)
    
    chainA1.setParam(condition1)
    

    整体感官上,是不是像竹子一样?每一节(函数输入、输出)特别清晰。关键是,它解耦了,组装起来也超级方便~

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    * 图片来源:守护安东尼,未经允许,随意转载。

    核心的,生成 Chain 的代码如下:

    Chain 函数是高级函数,入参是一个函数。这里通过原型链的方式给它加了 next、setParam 两个属性。next 的入参也是 fn,用于设置下一个处理函数,setParam 用于传递原始入参;

    var Chain = function( fn ){
      this.fn = fn;
      this.successor = null;
    };
    Chain.prototype.next = function( successor ){
      return this.successor = successor;
    };
    Chain.prototype.setParam = function(){
      var ret = this.fn.apply( this, arguments );
      if ( ret === 'doNext' ){
        return this.successor && this.successor.setParam.apply( this.successor, arguments );
      }
      return ret;
    };
    

    ?函数特性AOP

    实际上,利用 JavaScript 的函数式特性,还有一种更加方便的方法来创建责任链 —— 即 AOP。

    面向切面编程(AOP:Aspect Oriented Program)思想的简单理解:动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

    代码如下:

    /**
     * 函数交织(AOP)
     * @param {*} fn
     * @returns
     */
    
    Function.prototype.before = function(fn) {
      const self = this
      return function(...args) {
        const result = fn.apply(null, args)
        return self.call(null, result)
      }
    }
    
    Function.prototype.after = function(fn) {
      const self = this
      return function(...args) {
        const result = self.apply(null, args)
        return fn.call(null, result)
      }
    }
    

    调用示例:

    fn1 = step2.before(init).after(step3).after(step4)
    
    //fn1 = init -> step2 -> step3 -> step4
    

    我们可以任意指定、搭配函数的执行先后关系;

    ?composeAOP

    还记得《感谢 compose 函数,让我的代码屎山?逐渐美丽了起来~》这篇文章吗?compose 其实有很多种写法!我们可以借助上面的 before 和 after 函数实现这一版的 composeAOP ~

    const composeAOP = function(...args) {
      const before = args.pop()
      const start = args.pop()
      if (args.length) {
        return args.reduce(function(f1, f2) {
          return f1.after(f2)
        }, start.before(before))
      }
      return start.before(before)
    }
    

    对了,回答之前有不少人问为啥 compose 是从右至左执行??

    const compose = function(...args) {
      if (args.length) {
        return args.reverse().reduce(function(f1, f2) {
          return f1.after(f2)
        })
      }
    }
    
    compose(step4,step3,step2,step1,init)("start")
    

    这里说一下:原因是它模拟了通常情况下函数逐层调用,层层包裹的顺序,像剥洋葱一样,从外而内,从右至左去解析:

    step4(step3(step2(step1(init(...args))))) // 一层层括号像极了洋葱皮
    

    如果你喜欢从左至右,或换 pop()shift() ,或去掉那层 reverse() 即可,或改 afterbefore......顺序问题,无关好坏,全凭喜好~

    ?传参问题!!

    如果你有心在控制台试试以上代码,不难发现其中的一个很严重的传参问题!!这个问题在《compose 优化屎山》那篇文章实际上也存在,也有细心的掘友反馈。

    function init(...args){
        console.log(args)
        return [...args,"init"]
    }
    function step1(...args){
        console.log(args)
        return [...args,"step1"]
    }
    function step2(...args){
        console.log(args)
        return [...args,"step2"]
    }
    function step3(...args){
        console.log(args)
        return [...args,"step3"]
    }
    
    compose(step3,step2,step1,init)("start")
    

    随着参数的传递,args 数组的维度在不断上升。

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    如果我们使用 flat(Infinity) 拉平数组,传参就变成了这样:

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    这样做有一个很大的问题就是:需要对照数组的传参顺序!这是很头疼的,因为保不定哪天就要增删改流程参数。

    所以,期望是能换成对象作传参,消除按顺序传参的桎梏。比如:

    {start:"start",init:"init",step1:"step1"......}
    

    直接动手试试:

    function init(...args){
        console.log(JSON.stringify(args))
        return {args:args,init:"init"}
    }
    function step1(...args){
        console.log(JSON.stringify(args))
        return {args:args,step1:"step1"}
    }
    function step2(...args){
        console.log(JSON.stringify(args))
        return {args:args,step2:"step2"}
    }
    function step3(...args){
        console.log(JSON.stringify(args))
        return {args:args,step3:"step3"}
    }
    
    compose(step3,step2,step1,init)("start")
    

    得到: 从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    显然这不是我们想要的,我们得再不断打印寻找规律:

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    哇噢~

    step3 中想获取 step1,就要 2 个 .args[0]

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    step2 中想获取 step1,只要 1 个 .args[0]

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    我们基本可以推出:想获得前 N 步的参数,只需带 N 个 .args[0]

    于是乎,我们可以尝试写一个 getCountStepAttr() 函数,用于在某个函数步骤中,获得前第 N 步的入参,通过调用对象属性的方式!

    来吧,展翅~

    function getCountStepAttr(args,N){
        // 需要前第几(N)步的参数
        N = N -1
        let resObj = args[0]
        for(let i =0;i<N;i++){
            resObj = resObj.args[0]
        }
        return resObj
    }
    

    直接就可以测试使用了:

    从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    ?完整代码

    贴下完整代码,你可以拷贝在控制台玩一玩看看,本瓜相信你一定会有所收获!!

    Function.prototype.after = function(fn) {
      const self = this
      return function(...args) {
        let result = self.apply(null, args)
        return fn.call(null,result)
      }
    }
    const compose = function(...args) {
      if (args.length) {
        return args.reverse().reduce(function(f1, f2) {
          return f1.after(f2)
        })
      }
    }
    const getCountStepAttr = function(args,N){
        // 获取前 N 步的入参;
        N = N -1
        let resObj = args[0]
        for(let i =0;i<N;i++){
            resObj = resObj.args[0]
        }
        return resObj
    }
    function init(...args){
        console.log("【在 init 中调用原始传参】:",getCountStepAttr(args,1))
        return {args:args,init1:"init1",init:"init"}
    }
    function step1(...args){
        return {args:args,step1:"step1"}
    }
    function step2(...args){
        return {args:args,step2:"param-step2",step2Add:"param-step2-add"}
    }
    function step3(...args){
        console.log("【在 step3 中调用 step2 的传参】:",getCountStepAttr(args,1).step2 , getCountStepAttr(args,1).step2Add)
        console.log("【在 step3 中调用 init 的传参】:",getCountStepAttr(args,3).init , getCountStepAttr(args,3).init1)
        console.log("【在 step3 中调用原始传参】:",getCountStepAttr(args,4))
        return {args:args,step3:"step3"}
    }
    compose(step3,step2,step1,init)("start")
    

    ?小结展望

    本篇在讲什么?

    其实还是那金光闪闪的五个大字:函数式编程

    我们将过程中的命令式代码用一个个简单的纯函数进行封装,最后组合成各种丰富的功能。

    你可以在这个过程中,或任意拆卸、或增添补充、或重构设计,真的不用太担心隐藏的逻辑错漏或耦合造成的复杂业务难梳理!

    我们用函数的输入、输出表达映射关系,用函数名表达函数内的功能实现,用参数的传递表达业务逻辑,用封闭的作用域环境构造干净的代码~

    当然,你或许还有很多好的想法,代码的干净之路 还有很长一段要走!高山仰止,景行行止,虽不能至,心向往之。再说,能不能“至”还真不一定呢!

    都看到这里,不如点个赞吧 ??? 撰文不易,多谢鼓励 ???

    欢迎点赞、收藏、评论~


    起源地下载网 » 从【if...else...】到【责任链】再到【composeAOP】,顺带把【传参】解决了~

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元