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 相关文章推荐
对textarea框的代码调试,而且功能上使用非常方便,酷
Jun 30 Javascript
JavaScript 异步调用框架 (Part 3 - 代码实现)
Aug 04 Javascript
JavaScript 未结束的字符串常量常见解决方法
Jan 24 Javascript
javascript天然的迭代器
Oct 29 Javascript
JS事件Event元素(兼容IE,Firefox,Chorme)
Nov 01 Javascript
使用闭包对setTimeout进行简单封装避免出错
Jul 10 Javascript
JS去掉字符串前后空格、阻止表单提交的实现代码
Jun 08 Javascript
简单实现jQuery上传图片显示预览功能
Jun 29 jQuery
为vue-router懒加载时下载js的过程中添加loading提示避免无响应问题
Apr 03 Javascript
利用d3.js实现蜂巢图表带动画效果
Sep 03 Javascript
ES6函数实现排它两种写法解析
May 13 Javascript
Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互的实例
Nov 18 Vue.js
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
咖啡冲泡指南 咖啡有哪些制作方式 单品咖啡 意式咖啡
2021/03/06 冲泡冲煮
Win9x/ME下Apache+PHP安装配置
2006/10/09 PHP
PHPAnalysis中文分词类详解
2014/06/13 PHP
PHP 布尔值的自增与自减的实现方法
2018/05/03 PHP
laravel中Redis队列监听中断的分析
2020/09/14 PHP
JavaScript var声明变量背后的原理示例解析
2013/10/12 Javascript
js简单实现用户注册信息的校验代码
2013/11/15 Javascript
JS图片无缝、平滑滚动代码
2014/03/11 Javascript
使用JavaScript实现弹出层效果的简单实例
2016/05/31 Javascript
BootStrap的table表头固定tbody滚动的实例代码
2016/08/24 Javascript
用nodejs搭建websocket服务器
2017/01/23 NodeJs
详解angular2封装material2对话框组件
2017/03/03 Javascript
深入浅出es6模板字符串
2017/08/26 Javascript
Vue组件之全局组件与局部组件的使用详解
2017/10/09 Javascript
详解vue-cli项目中的proxyTable跨域问题小结
2018/02/09 Javascript
Express进阶之log4js实用入门指南
2018/02/10 Javascript
node实现生成带参数的小程序二维码并保存到本地功能示例
2018/12/05 Javascript
使用JavaScript实现贪吃蛇游戏
2020/09/29 Javascript
windows10系统中安装python3.x+scrapy教程
2016/11/08 Python
Python安装pycurl失败的解决方法
2018/10/15 Python
pycharm如何设置官方中文(如何汉化)
2020/12/29 Python
canvas画布实现手写签名效果的示例代码
2019/04/23 HTML / CSS
戴尔美国官方折扣店:Dell Outlet
2018/02/13 全球购物
使用索引(Index)有哪些需要考虑的因素
2016/10/19 面试题
大四学生思想汇报
2014/01/13 职场文书
中专毕业生自我鉴定
2014/02/02 职场文书
移交协议书
2014/08/19 职场文书
农业局党的群众路线教育实践活动整改方案
2014/09/20 职场文书
2015中学教师个人工作总结
2015/07/22 职场文书
2016秋季小学开学寄语
2015/12/03 职场文书
廉洁自律准则学习心得体会
2016/01/13 职场文书
详解用Python把PDF转为Word方法总结
2021/04/27 Python
html+css实现赛博朋克风格按钮
2021/05/26 HTML / CSS
vue项目多环境配置(.env)的实现
2021/07/21 Vue.js
详细聊一聊mysql的树形结构存储以及查询
2022/04/05 MySQL
MySQL查询日期时间
2022/05/15 MySQL