JavaScript 链式结构序列化详解


Posted in Javascript onSeptember 30, 2016

一、概述

在JavaScript中,链式模式代码,太多太多,如下:

if_else:

if(...){
  //TODO
}else if(...){
  //TODO
}else{
  //TODO
}

switch:

switch(name){
  case ...:{
    //TODO
    break;
  }
  case ...:{
    //TODO
    break;
  }
  default:{
    //TODO  
  }
}

疑问:诸如上述这些链式代码,倘若,我们想将其扁平化链式处理呢?如下:

//fn1,f2,f3为处理函数
_if(fn1)._elseIf(fn2)._else(fn3);

下面我们就来一起尝试实现下呗。

二、链式代码扁平化

假如,现在我们有如下链式代码:

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!');
}

好了,现在我们一步一步将其"扁平化"。

其实看看上面的代码,不难发现,if…else这种格式,其实就是数据结构中的单链表,那么,初步利用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);

其中f1,f2,f3为判断函数,并且我们假设,如果诸如f1、f2、f3返回'next'时,就继续往下查找,否则,停止往下查找。如下:

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!');
}

好了,这就是链表的模式。

但是,我们的最终目的是想实现如下这样的呢?

//fn1,f2,f3为处理函数
_if(fn1)._elseIf(fn2)._else(fn3);

你可能会说,将上述代码改成如下这样,不就好了吗?!!

thens.push(f1).push(f2).push(f3).resolve();

But,JavaScript的push方法返回的是数组的新长度,而不是数组对象哦。

So,那我们只能新写一个add方法,效果和push一样,但是返回数组对象。如下:

thens.add = function(f){
  if(typeof f === 'function'){
    this.push(f);
    return this;    
  }    
}

测试代码如下:

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();

但是,这样有个缺点,我们是将add、resolve方法绑定在全局变量thens中的,总不能每次创建一个数组时,都复制粘贴一遍方法吧,所以重构代码如下:

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;
      }
    }  
  }
}

显然,add,resolve这种公共方法,在每次实例化时,都创建一遍是不科学的,so,利用prototype在原有的基础上继续变形,如下:

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;
        }
      }  
  }
}

测试代码如下:

var thens = new Slink();
thens.add(f1).add(f2).add(f3);
thens.resolve();

不错,但是这样,我们每次都得手动new一个Slink,有点麻烦,所以,我们将new Slink这个过程,封装到函数中,如同jQuery一样,如下:

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;
        }
      }  
  }
}

测试代码如下:

$go(f1).add(f2).add(f3).resolve();

好了,大功告成,接下来就是语法糖滴问题咯,整理代码如下:

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;      
  }
}

测试代码如下:

_if(f1)._elseIf(f2)._else(f3).resolve();

当然,除开利用数组这种方式,还可以利用闭包,实现链式扁平化效果,如下:

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;
  }
}

测试代码如下:

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');

三、异步代码链式扁平化

在上面我们讨论的都是同步过程,倘若,链式调用函数中有异步情况呢?

什么意思?如下:

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!');
}

我们将f1利用setTimeout变成了异步,按照上述代码的逻辑,应该是等f1完全执行完毕(包括setTimeout执行)后,判断是否执行f2,但真的如此吗?

测试代码如下:

_if(f1)._elseIf(f2)._else(f3).resolve();

执行代码的结果就是,什么也不输出。

Why?

因为JavaScript是单线程嘛。详情见(here)

那该怎么解决呢?

由于有异步代码,且必须在异步代码后处理后续的链,那么我们就等待异步代码执行完毕后,才执行后续的链嘛,如下:

function f1(name){
  setTimeout(function(){
    if(name === 'Monkey'){
      console.log('yes, I am Monkey');
    }else{
      //处理后续链
      this.resolve(name, 1);//1代表下一个需处理函数在数组中的位置
    }
  }.bind(this), 2000);
}

好了,由于在函数中,我们使用了this,其代表Slink对象,且改变了resolve方法,固,需细微调整Slink构造函数和原型链,如下:

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;      
  }
}

测试代码如下:

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);

哈哈,如果你了解Promise,是不是感觉这么相似呢。

是的,宗旨都一样,达到异步代码扁平化目的,不过这里的代码比Promise要简约得多啦。关于Promise详情见(here)。

感谢阅读此文,希望能帮助到大家,谢谢大家对本站的支持!

Javascript 相关文章推荐
些很实用且必用的小脚本代码
Jun 26 Javascript
javascript document.images实例
May 27 Javascript
javascript 鼠标滚轮事件
Apr 09 Javascript
jquery对元素拖动排序示例
Jan 16 Javascript
jQuery unbind()方法实例详解
Jan 19 Javascript
JS组件Bootstrap实现弹出框效果代码
Apr 26 Javascript
谈一谈bootstrap响应式布局
May 23 Javascript
Javascript实现基本运算器
Jul 15 Javascript
解决vue router使用 history 模式刷新后404问题
Jul 19 Javascript
javaScript字符串工具类StringUtils详解
Dec 08 Javascript
解决vuejs 使用value in list 循环遍历数组出现警告的问题
Sep 26 Javascript
如何手写一个简易的 Vuex
Oct 10 Javascript
Bootstrap3 Grid system原理及应用详解
Sep 30 #Javascript
CSS3 media queries结合jQuery实现响应式导航
Sep 30 #Javascript
微信小程序 LOL 英雄介绍开发实例
Sep 30 #Javascript
javascript的几种写法总结
Sep 30 #Javascript
Vue.js实现拖放效果的实例
Sep 30 #Javascript
PHP抓取HTTPS内容和错误处理的方法
Sep 30 #Javascript
Vue.js动态添加、删除选题的实例代码
Sep 30 #Javascript
You might like
PHP n个不重复的随机数生成代码
2009/06/23 PHP
避免Smarty与CSS语法冲突的方法
2015/03/02 PHP
Zend Framework实现留言本分页功能(附demo源码下载)
2016/03/22 PHP
利用Laravel生成Gravatar头像地址的优雅方法
2017/12/30 PHP
jquery 全局AJAX事件使用代码
2010/11/05 Javascript
仅IE6/7/8中innerHTML返回值忽略英文空格的问题
2011/04/07 Javascript
javascript 中that的含义示例介绍
2014/05/14 Javascript
javascript获取四位数字或者字母的随机数
2015/01/09 Javascript
jQuery中wrapAll()方法用法实例
2015/01/16 Javascript
Function.prototype.apply()与Function.prototype.call()小结
2016/04/27 Javascript
对jQuary选择器的全面总结
2016/06/20 Javascript
AngularJS 过滤器(自带和自建)详解
2016/09/19 Javascript
微信小程序页面生命周期详解
2018/01/31 Javascript
vue webpack打包优化操作技巧
2018/02/22 Javascript
微信小程序缓存过期时间的使用详情
2019/05/12 Javascript
ES6基础之 Promise 对象用法实例详解
2019/08/22 Javascript
微信小程序实现吸顶效果
2020/01/08 Javascript
[45:18]完美世界DOTA2联赛循环赛 PXG vs IO 第二场 11.06
2020/11/09 DOTA
python中使用pyhook实现键盘监控的例子
2014/07/18 Python
python利用lxml读写xml格式的文件
2017/08/10 Python
零基础使用Python读写处理Excel表格的方法
2019/05/02 Python
python中几种自动微分库解析
2019/08/29 Python
python中count函数简单的实例讲解
2020/02/06 Python
Python连接Oracle之环境配置、实例代码及报错解决方法详解
2020/02/11 Python
python如何提升爬虫效率
2020/09/27 Python
Python通过递归函数输出嵌套列表元素
2020/10/15 Python
Python虚拟环境virtualenv创建及使用过程图解
2020/12/08 Python
音乐器材管理制度
2014/01/31 职场文书
会计专业大学生职业生涯规划书
2014/02/11 职场文书
个人四风问题对照检查材料思想汇报
2014/10/06 职场文书
宿舍卫生管理制度
2015/08/05 职场文书
中学政教处工作总结
2015/08/13 职场文书
运动会广播稿100字
2015/08/19 职场文书
2016年感恩父亲节活动总结
2016/04/01 职场文书
简单介绍 http请求响应参数、无连接无状态、MIME、状态码、端口、telnet、curl
2021/03/31 HTML / CSS
JS新手入门数组处理的实用方法汇总
2021/04/07 Javascript