JavaScript链式结构序列化详解

FletcherPer 9年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/04fbb033c0db7ed40e613381f96f746b.jpg"></p>    <h3><strong>一、概述</strong></h3>    <p>在JavaScript中,链式模式代码,太多太多,如下:</p>    <p>if_else:</p>    <pre>  <code class="language-javascript">if(...){       //TODO   }else if(...){       //TODO   }else{       //TODO   }   </code></pre>    <p>switch:</p>    <pre>  <code class="language-javascript">switch(name){       case ...:{           //TODO           break;       }       case ...:{           //TODO           break;       }       default:{           //TODO           }   }   </code></pre>    <p>疑问:诸如上述这些链式代码,倘若,我们想将其扁平化链式处理呢?如下:</p>    <pre>  <code class="language-javascript">//fn1,f2,f3为处理函数   _if(fn1)._elseIf(fn2)._else(fn3);   </code></pre>    <p>下面我们就来一起尝试实现下呗。</p>    <h3><strong>二、链式代码扁平化</strong></h3>    <p>假如,现在我们有如下链式代码:</p>    <pre>  <code class="language-javascript">if(name === 'Monkey'){       console.log('yes, I am Monkey');   }else if(name === 'Dorie'){       console.log('yes, I am Dorie');   }else{       console.log('sorry, over for ending!');   }   </code></pre>    <p>好了,现在我们一步一步将其”扁平化”。</p>    <p>其实看看上面的代码,不难发现,if…else这种格式,其实就是数据结构中的单链表,那么,初步利用JavaScript实现单链表,如下:</p>    <pre>  <code class="language-javascript">var thens = [];   thens.resolve = function(name){       for(var i = 0, len = this.length; i < len;i++){           if(this[i](name) !== 'next'){               break;           }       }   }   thens.push(f1, f2, f3);   </code></pre>    <p>其中f1,f2,f3为判断函数,并且我们假设,如果诸如f1、f2、f3返回’next’时,就继续往下查找,否则,停止往下查找。如下:</p>    <pre>  <code class="language-javascript">function f1(name){       if(name === 'Monkey'){           console.log('yes, I am Monkey');       }else{           return 'next';       }   }   function f2(name){       if(name === 'Dorie'){           console.log('yes, I am Dorie');       }else{           return 'next';       }   }   function f3(){       console.log('sorry, over for ending!');   }   </code></pre>    <p>好了,这就是链表的模式。</p>    <p>但是,我们的最终目的是想实现如下这样的呢?</p>    <pre>  <code class="language-javascript">//fn1,f2,f3为处理函数   _if(fn1)._elseIf(fn2)._else(fn3);   </code></pre>    <p>你可能会说,将上述代码改成如下这样,不就好了吗?!!</p>    <pre>  <code class="language-javascript">thens.push(f1).push(f2).push(f3).resolve();   </code></pre>    <p>But,JavaScript的push方法返回的是数组的新长度,而不是数组对象哦。</p>    <p>So,那我们只能新写一个add方法,效果和push一样,但是返回数组对象。如下:</p>    <pre>  <code class="language-javascript">thens.add = function(f){       if(typeof f === 'function'){           this.push(f);           return this;               }           }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">var thens = [];   thens.add = function(f){       if(typeof f === 'function'){           this.push(f);           return this;               }           }   thens.resolve = function(name){       for(var i = 0, len = this.length; i < len;i++){           if(this[i](name) !== 'next'){               break;           }       }       }   thens.add(f1).add(f2).add(f3).resolve();   </code></pre>    <p>但是,这样有个缺点,我们是将add、resolve方法绑定在全局变量thens中的,总不能每次创建一个数组时,都复制粘贴一遍方法吧,所以重构代码如下:</p>    <pre>  <code class="language-javascript">function Slink(){       this.thens = [];       this.thens.add = function(f){           if(typeof f === 'function'){               this.push(f);               return this;                   }               }       this.thens.resolve = function(name){           for(var i = 0, len = this.length; i < len;i++){               if(this[i](name) !== 'next'){                   break;               }           }           }   }   </code></pre>    <p>显然,add,resolve这种公共方法,在每次实例化时,都创建一遍是不科学的,so,利用prototype在原有的基础上继续变形,如下:</p>    <pre>  <code class="language-javascript">function Slink(){       this.thens = [];   }   Slink.prototype = {       add: function(f){               if(typeof f === 'function'){                   this.thens.push(f);                   return this;                       }               },       resolve: function(name){               for(var i = 0, len = this.thens.length; i < len; i++){                   if(this.thens[i](name) !== 'next'){                       break;                   }               }           }   }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">var thens = new Slink();   thens.add(f1).add(f2).add(f3);   thens.resolve();   </code></pre>    <p>不错,但是这样,我们每次都得手动new一个Slink,有点麻烦,所以,我们将new Slink这个过程,封装到函数中,如同jQuery一样,如下:</p>    <pre>  <code class="language-javascript">function $go(f){       return new Slink(f);   }   function Slink(f){       this.thens = [];       this.thens.push(f);   }   Slink.prototype = {       add: function(f){               if(typeof f === 'function'){                   this.thens.push(f);                   return this;                       }               },       resolve: function(name){               for(var i = 0, len = this.thens.length; i < len; i++){                   if(this.thens[i](name) !== 'next'){                       break;                   }               }           }   }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">$go(f1).add(f2).add(f3).resolve();   </code></pre>    <p>好了,大功告成,接下来就是语法糖滴问题咯,整理代码如下:</p>    <pre>  <code class="language-javascript">function _if(f){       return new Slink(f);   }   function Slink(f){       this.thens = [];       this.thens.push(f);   }   Slink.prototype = {       _elseIf: function(f){               if(typeof f === 'function'){                   this.thens.push(f);                   return this;                       }               },       _else: function(f){               return this._elseIf(f);       },       resolve: function(name){               for(var i = 0, len = this.thens.length; i < len; i++){                   if(this.thens[i](name) !== 'next'){                       break;                   }               }               return this;                   }   }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">_if(f1)._elseIf(f2)._else(f3).resolve();   </code></pre>    <p>当然,除开利用数组这种方式,还可以利用 闭包 ,实现链式扁平化效果,如下:</p>    <pre>  <code class="language-javascript">var func = Function.prototype;   func._else = func._elseIf = function(fn){       var _this = this;       return function(){           var res = _this.apply(this,arguments);           if(res==="next"){  //值为Boolean               return fn.apply(this,arguments);           }           return res;       }   }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">function f1(name){       if(name === 'Monkey'){           console.log('yes, I am Monkey');       }else{           return 'next';       }   }   function f2(name){       if(name === 'Dorie'){           console.log('yes, I am Dorie');       }else{           return 'next';       }   }   function f3(){       console.log('sorry, over for ending!');   }   f1._elseIf(f2)._else(f3)('Dorie');   </code></pre>    <h3><strong>三、异步代码链式扁平化</strong></h3>    <p>在上面我们讨论的都是同步过程,倘若,链式调用函数中有异步情况呢?</p>    <p>什么意思?如下:</p>    <pre>  <code class="language-javascript">function f1(name){       setTimeout(function(){           if(name === 'Monkey'){               console.log('yes, I am Monkey');           }else{               return 'next';           }       }, 2000);   }   function f2(name){       if(name === 'Dorie'){           console.log('yes, I am Dorie');       }else{           return 'next';       }   }   function f3(){       console.log('sorry, over for ending!');   }   </code></pre>    <p>我们将f1利用setTimeout变成了异步,按照上述代码的逻辑,应该是等f1完全执行完毕(包括setTimeout执行)后,判断是否执行f2,但真的如此吗?</p>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">_if(f1)._elseIf(f2)._else(f3).resolve();   </code></pre>    <p>执行代码的结果就是,什么也不输出。</p>    <h3><strong>Why?</strong></h3>    <p>因为JavaScript是单线程嘛。</p>    <p>那该怎么解决呢?</p>    <p>由于有异步代码,且必须在异步代码后处理后续的链,那么我们就等待异步代码执行完毕后,才执行后续的链嘛,如下:</p>    <pre>  <code class="language-javascript">function f1(name){       setTimeout(function(){           if(name === 'Monkey'){               console.log('yes, I am Monkey');           }else{               //处理后续链               this.resolve(name, 1);//1代表下一个需处理函数在数组中的位置           }       }.bind(this), 2000);   }   </code></pre>    <p>好了,由于在函数中,我们使用了this,其代表Slink对象,且改变了resolve方法,固,需细微调整Slink构造函数和原型链,如下:</p>    <pre>  <code class="language-javascript">function Slink(f){       this.thens = [];       this.thens.push(f.bind(this));   }   Slink.prototype = {       _elseIf: function(f){               if(typeof f === 'function'){                   this.thens.push(f.bind(this));                   return this;                      }              },       _else: function(f){               return this._elseIf(f.bind(this));       },       resolve: function(name, flag){               for(var i = flag, len = this.thens.length; i < len; i++){                   if(this.thens[i](name) !== 'next'){                       break;                   }               }               return this;                  }   }   </code></pre>    <p>测试代码如下:</p>    <pre>  <code class="language-javascript">function f1(name){       setTimeout(function(){           if(name === 'Monkey'){               console.log('yes, I am Monkey');           }else{               //处理后续链               this.resolve(name, 1);//1代表下一个需处理函数在数组中的位置           }       }.bind(this), 2000);   }   function f2(name){       if(name === 'Dorie'){           console.log('yes, I am Dorie');       }else{           return 'next';       }   }   function f3(){       console.log('sorry, over for ending!');   }   _if(f1)._elseIf(f2)._else(f3).resolve('',0);   </code></pre>    <p>哈哈,如果你了解Promise,是不是感觉这么相似呢。</p>    <p>是的,宗旨都一样,达到异步代码扁平化目的,不过这里的代码比Promise要简约得多啦。</p>    <p> </p>    <p> </p>    <p>来自:http://developer.51cto.com/art/201612/525374.htm</p>    <p> </p>