Skip to content

《JavaScript设计模式》技巧型设计模式

链模式

通过在对象方法上将当前对象返回,实现对同一个对象多个方法的链式调用

js
var A = function(selector) {
  return new A.fn.init(selector);
};
A.fn = A.prototype = {
  constructor: A,
  init: function(selector) {
    this[0] = document.getElementById(selector);
    this.length = 1;
    return this;
  },
  length: 2,
  size: function() {
    return this.length;
  }
};
A.fn.init.prototype = A.fn;

console.log(A("demo").size());
  • constructor: A 》 constructor默认为a.fn.A.init的构造函数
  • A.fn.init.prototype = A.fn 》 避免方法丢失

方法扩展

js
//对象拓展
A.extend = A.fn.extend = function() {
  //拓展对象从第二个参数算起
  var i = 1,
    //获取参数长度
    len = arguments.length,
    //第一个参数为源对象
    target = arguments[0],
    //拓展对象中属性
    j;
  //如果只穿一个参数
  if (i == len) {
    //源对象为当前对象
    target = this;
    //i从0计数
    i--;
  }
  //遍历参数中拓展对象
  for (; i < len; i++) {
    //遍历拓展对象中的属性
    for (j in arguments[i]) {
      //拓展源对象
      target[j] = arguments[i][j];
    }
  }
  //返回源对象
  return target;
};

var demo = A.extend({ first: 1 }, { second: 2 }, { third: 3 });
console.log(demo);
A.extend(A.fn, { version: "1.0" });
console.log(A("demo").version);
A.fn.extend({
  getVersion: function() {
    return this.version;
  }
});
console.log(A("demo").getVersion());
A.extend(A, { names: "张三" });
console.log(A.names);

添加方法

js
A.extend({
  //分割带“-”样式,变为驼峰式写法
  camelCase: function(str) {
    return str.replace(/\-(\w)/g, function(all, letter) {
      return letter.toUpperCase();
    });
  }
});

A.fn.extend({
  //设置css样式
  css: function() {
    var arg = arguments,
      len = arg.length;
    if (this.length < 1) {
      return this;
    }
    //只有一个参数时
    if (len === 1) {
      //如果为字符串则为获取一个元素CSS样式
      if (typeof arg[0] === "string") {
        //IE
        if (this[0].currentStyle) {
          return this[0].currentStyle[name];
        } else {
          return getComputedStyle(this[0], false)[name];
        }
        //为对象时则设置多个样式
      } else if (typeof arg[0] === "object") {
        //遍历每个样式
        for (var i in arg[0]) {
          for (var j = this.length - 1; j >= 0; j--) {
            //分割-为驼峰式写法
            this[j].style[A.camelCase(i)] = arg[0][i];
          }
        }
      }
      //两个参数则设置一个样式
    } else if (len === 2) {
      for (var j = this.length - 1; j >= 0; j--) {
        this[j].style[A.camelCase(arg[0])] = arg[1];
      }
    }
    return this;
  }
});

A.fn.extend({
  //设置属性
  attr: function() {
    var arg = arguments,
      len = arg.length;
    if (this.length < 1) {
      return this;
    }
    //如果一个参数
    if (len === 1) {
      //为字符串获取第一个元素属性
      if (typeof arg[0] === "string") {
        return this[0].getAttribute(arg[0]);
      }
      //为对象设置每个元素的多个属性
      else if (typeof arg[0] === "object") {
        //遍历属性
        for (var i in arg[0]) {
          for (var j = this.length - 1; j >= 0; j--) {
            this[j].setAttribute(i, arg[0][i]);
          }
        }
      }
      //两个参数则设置每个元素单个属性
    } else if (len === 2) {
      for (var j = this.length - 1; j >= 0; j--) {
        this[j].setAttribute(arg[0], arg[i]);
      }
    }
    return this;
  }
});

A.fn.extend({
  //获取或设置元素的内容
  html: function() {
    var arg = arguments,
      len = arg.length;
    //如果没参数则取第一个元素的内容
    if (len === 0) {
      return this[0] && this[0].innerHTML;
    } else {
      //一个参数则设置每个元素的内容
      for (var i = this.length - 1; i >= 0; i--) {
        this[i].innerHTML = arg[0];
      }
    }
    return this;
  }
});
A.fn.extend({
  //添加时间
  on: (function() {
    //如果支持DOM2级事件
    if (document.addEventListener) {
      return function(type, fn) {
        var i = this.length - 1;
        for (; i >= 0; i--) {
          this[i].addEventListener(type, fn, false);
        }
        return this;
      };
      //IE浏览器DOM2级事件
    } else if (document.attachEvent) {
      return function(type, fn) {
        var i = this.length - 1;
        for (; i >= 0; i--) {
          this[i].addEvent("on" + type, fn);
        }
        return this;
      };
      //不支持DOM2级浏览器添加事件
    } else {
      return function(type, fn) {
        var i = this.length - 1;
        for (; i >= 0; i--) {
          this[i]["on" + type] = fn;
        }
        return this;
      };
    }
  })()
});
A("demo")
  .css({
    height: "30px",
    borer: "1px solid #000",
    "background-color": "red"
  })
  .attr("class", "demo")
  .html("添加文字")
  .on("click", function() {
    console.log("触发点击事件");
  });

委托模式

多个对象接受并处理同一请求,他们将请求委托给另一个对象统一处理

js
ul.onclick = function(e) {
 if (e.target.nodeName.toLowerCase() === "li") {
   e.target.style.backgroundColor = "red";
 }
  • 委托父元素
  • 内存外泄(老版本IE引用计数式垃圾回收,移除元素,click事件没有清除;新版本为标记清除管理,不会有这个问题)

数据访问对象模式

抽象和封装对数据源的访问与存储,DAO通过对数据链接的管理方便对数据的访问与存储

js
/*
 * 本地存储类
 * 参数preId 本地数据存储库前缀
 * 参数timeSign 时间戳与存储数据之间的拼接符
 */
var BaseLocalStorege = function(preId, timeSign) {
  //定义本地存储数据库前缀
  this.preId = preId;
  //定义时间戳与存储数据之间的拼接符
  this.timeSign = timeSign || "|-|";
};
//本地存储类 原型方法
BaseLocalStorege.prototype = {
  //操作状态
  status: {
    Success: 0, //成功
    FaiLure: 1, //失败
    OverFlow: 2, //溢出
    TimeOut: 3 //过期
  },
  //保存本地存储连接
  storage: localStorage || window.localStorage,
  //保存本地存储数据库数据真实字段
  getKey: function(key) {
    return this.preId + key;
  },
  //添加(修改)数据
  /*
   * 参数key:数据字段标识
   * 参数value:数据值
   * 参数callback:回调函数
   * 参数time:添加时间
   */
  set: function(key, value, callback, time) {
    //默认操作状态时成功
    var status = this.status.Success,
      //获取真实字段
      key = this.getKey(key);
    try {
      time = new Date(time).getTime() || time.getTime();
    } catch (e) {
      //TODO handle the exception
      //为传入时间参数或者时间参数有误获取默认时间:一个月
      time = new Date().getTime() + 1000 * 60 * 60 * 24 * 31;
    }
    try {
      //向数据库中添加数据
      this.storage.setItem(key, time, +this.timeSign + value);
    } catch (e) {
      //溢出失败,返回溢出状态
      //TODO handle the exceptionOverFlow
      status = this.status.OverFlow;
    }
    //有回调函数则执行回调函数并传入参数操作状态,真实数据字段标识以及存储数据值
    callback && callback.call(this, status, key, value);
  },
  /*
   * 添加(修改)数据
   * 参数key :数据字段标识
   * 参数callback:回调函数
   */
  get: function(key, callback) {
    //默认操作状态时成功
    var status = this.status.Success,
      //获取
      key = this.getKey(key),
      //默认值为空
      value = null,
      //时间戳与存储数据之间的拼接符长度
      timeSignLen = this.timeSign.length,
      //缓存当前对象
      that = this,
      //时间戳与存储数据之间的拼接符起始位置
      index,
      //时间戳
      time,
      //最终获取的数据
      result;
    try {
      //获取字段对应的数据字符串
      value = that.storage.getItem(key);
    } catch (e) {
      //TODO handle the exception
      //获取失败,则返回失败状态,数据结果为null
      result = {
        status: that.status.FaiLure,
        value: null
      };
      callback && callback.call(this, result.status, result.value);
      return result;
    }
    //如果成功获取数据字符串
    if (value) {
      //获取时间戳与存储数据之间的拼接符起始位置
      index = value.indexOf(that.timeSign);
      //获取时间戳
      time = +value.slice(0, index);
      //如果时间过期
      if (new Date(time).getTime() > new Date().getTime() || time == 0) {
        //获取数据结果(拼接符后面的字符串)
        value = value.slice(index + timeSignLen);
      } else {
        //过期则结果为null
        value = null;
        //设置状态过期为过期状态
        status = that.status.TimeOut;
        //删除该字段
        that.remove(key);
      }
    } else {
      //未获取数据字符串状态为失败状态
      status = that.status.FaiLure;
    }
    //设置结果
    result = {
      status: status,
      value: value
    };
    //执行回调函数
    callback && callback.call(this, result.status, result.value);
  },
  /*
   * 删除数据
   * 参数key:数据字段标识
   * 参数 callback:回调函数
   */
  remove: function(key, callback) {
    //设置默认初始状态为失败
    var status = this.status.FaiLure,
      key = this.getKey(key),
      //设置默认数据结果为空
      value = null;
    try {
      //获取字段对应的数据
      value = this.storage.getItems(key);
    } catch (e) {
      //TODO handle the exception
      //如果数据存在
      if (value) {
        try {
          //删除数据
          this.storage.removeItem(key);
          //设置操作成功
          status = this.status.Success;
        } catch (e) {
          //TODO handle the exception
        }
      }
      //执行回调 注意传入回调函数中的数据值:如果操作状态成功则返回真实的数据结果,否则返回空
      callback &&
        callback.call(
          this,
          status,
          status > 0
            ? null
            : value.slice(
                value.indexOf(this.timeSign) + this.timeSign.length
              )
        );
      return callback;
    }
  }
};

var LS = new BaseLocalStorege("LS_");
//在数据库中创建a变量 然后删除两次 查看结果
LS.set("a", "xiaoming", function() {
  console.log(arguments);
}); //[0,"LS_a","xiaoming"]
LS.get("a", function() {
  console.log("get" + arguments);
}); //[0,"xiaoming"]
LS.remove("a", function() {
  console.log("removeOne" + arguments);
}); //[0,"xiaoming"]
LS.remove("a", function() {
  console.log("removeTwo" + arguments);
}); //[1,null]
LS.get("a", function() {
  console.log("getTwo" + arguments);
}); //[1,null]

节流模式

对重复业务逻辑进行节流控制,执行最后一次操作并取消其他操作

简单模板模式

通过格式化字符串拼凑出视图避免创建视图时大量节点操作。优化了内存开销

惰性模式

减少每次代码执行时的重复性分支判断,通过对对象重定义来屏蔽原对象中的分支判断

加载即执行

js
//加载即执行
A.on = (function() {
  if (document.addEventListener) {
    return function(dom, type, fn) {
      dom.addEventListener(type, fn, false);
    };
  } else if (document.attachEvent) {
    return function(dom, type, fn) {
      dom.attachEvent("on" + type, fn);
    };
  } else {
    return function(dom, type, fn) {
      dom["on+type"] = fn;
    };
  }
})();

惰性执行

js
A.on = function(dom, type, fn) {
  if (dom.addEventListener) {
    A.on = function(dom, type, fn) {
      dom.addEventListener(type, fn, false);
    };
  } else if (dom.attachEvent) {
    A.on = function(dom, type, fn) {
      dom.attachEvent("on" + type, fn);
    };
  } else {
    A.on = function(dom, type, fn) {
      dom["on" + type] = fn;
    };
  }

  A.on(dom, type, fn);
};

参与者模式

  • 在特定的作用域中执行给定的函数,并将参数原封不动的传递
  • call和apply方法可以使我们在特定作用域中执行某个函数,并传入参数

传递数据

js
var A = {} ;
// 事件绑定方法
A.event.on = function(dom, type, fn, data) {
 // w3c标准事件绑定
 if(dom.addEventListener) {
     dom.addEventListener(type, function(e) {
         fn.call(dom, e, data) ;
     }, false) ;
 }else if(dom.attachEvent) {
     dom.attachEvent('on' + type, function(e) {
         fn.call(dom, e, data) ;
     }) ;
 }else {
     dom['on' + type] = function(e) {
         fn.call(dom, e, data) ;
     }
 }
}

函数绑定

js
// 函数绑定bind
function bind(fn, context) {
 // 闭包返回新函数
 return function() {
     // 对fn装饰并返回
     return fn.apply(context, arguments) ;
 }
}

// 测试用例:
var demoObj = {
 title : '这是一个例子'
} ;
function demoFn() {
 console.log(this.title) ;
} ;
// 让demoObj参与demoFn的执行
var bindFn = bind(demoFn, demoObj) ;
demoFn() ;   // undefined
bindFn() ;   // 这是一个例子

函数柯里化

  • 将接受多个参数的函数转化为接受一部分参数的新函数,余下的参数保存下来,当函数调用时,返回传入的参数与保存的参数共同执行的结果

  • 对函数参数的分割

  • 多态(重载)

js
// 函数柯里化
function curry(fn) {
    // 缓存数组slice方法Array.prototype.slice
    var _slice = [].slice ;
    // 从第二个参数开始截取参数
    var args = _slice.call(arguments, 1) ;
    // 闭包返回新函数
    return function() {
        // 将参数(类数组)转化为数组
        var addArgs = _slice.call(arguments) ;
        // 拼接参数
        var allArgs = args.concat(addArgs) ;
        // 返回新函数
        return fn.apply(null, allArgs) ;
    }
}

// 测试用例
// 正常加法器
function add(num1, num2) {
    return num1 + num2 ;
}
// 加5加法器
function add5(num) {
    return add(5, num) ;
}
// 测试加法器
console.log('1 + 2 = ', add(1, 2)) ;
// 测试加5加法器
console.log('5 + 6 = ', add5(6)) ;
// 函数柯里化创建加5加法器
var curry_add5 = curry(add, 5) ;
console.log('5 + 7 = ', curry_add5(7)) ;
var curry_add7_8 = curry(add, 7, 8) ;
console.log('7 + 8 = ', curry_add7_8()) ;

反函数柯里化

方便对方法的调用

js
// 反柯里化
Function.prototype.uncurry = function () {
  // 保存当前对象
  var that = this;
  return function () {
    return Function.prototype.call.apply(that, arguments);
  }
}
// 测试用例
// 用Object.prototype.toString校验对象类型时:
// 获得校验方法
var toString = Object.prototype.toString.uncurry();
// 测试对象数据类型
console.log('toString(function(){}):', toString(function () {}));
console.log('toString([]):', toString([]));
// 用数组的push方法为对象添加数据成员:
// 保存数组push方法
var push = [].push.uncurry();
// 创建一个对象
var demoArr = {};
// 通过push方法为对象添加数据成员
push(demoArr, '第一个成员', '第二个成员');
console.log(demoArr);

等待者模式

  • 通过对多个异步进程监听,来触发未来发生的动作
  • 解决那些不确定先后完成的异步逻辑
js
var Waiter = function () {
  var dfd = [], //等待对象容器
    doneArr = [], //成功回调容器
    failArr = [], //失败回调容器
    slice = Array.prototype.slice,
    that = this;
  //监控对象类
  var Promise = function () {
    //监控对象是否解决成功状态
    this.resolved = false;
    //监控对象是否解决失败状态
    this.rejected = false;
  }
  Promise.prototype = {
    //解决成功
    resolve: function () {
      //设置当前监控状态是成功
      this.resolved = true;
      if (!dfd.length) return;
      //对象监控对象遍历如果任一个对象没有解决或者失败就返回
      for (var i = dfd.length - 1; i >= 0; i--) {
        if (dfd[i] && !dfd[i].resolved || dfd[i].rejected) {
          return;
        }
        dfd.splice(i, 1);
      }
      _exec(doneArr)
    },
    //解决失败
    reject: function () {
      //设置当前监控状态是失败
      this.rejected = true;
      //没有监控对象取消
      if (!dfd.length) return;
      //清楚监控对象
      dfd.splice(0)
      _exec(failArr)
    }
  }
  that.Deferred = function () {
    return new Promise();
  };
  //回调执行方法
  function _exec(arr) {
    var i = 0,
      len = arr.length;
    for (; i < len; i++) {
      try {
        arr[i] && arr[i]();
      } catch (e) {}
    }
  };
  //监控异步方法参数:监控对象
  that.when = function () {
    //设置监控对象
    dfd = slice.call(arguments);
    var i = dfd.length;
    //向前遍历监控对象,最后一个监控对象索引值length-1
    for (--i; i >= 0; i--) {
      //不存在监控对象,监控对象已经解决,监控对象失败
      if (!dfd[i] || dfd[i].resolved || dfd[i].rejected || !dfd[i] instanceof Promise) {
        dfd.splice(i, 1)
      }
    }
    //返回等待者对象
    return that;
  };
  //解决成功回调函数添加方法
  that.done = function () {
    //向成功毁掉函数容器中添加回调方法
    doneArr = doneArr.concat(slice.call(arguments));
    return that;
  };
  //解决失败回调函数添加方法
  that.fail = function () {
    //向失败回调函数中添加方法
    failArr = failArr.concat(slice.call(arguments));
    return that;
  };
}

//测试
var waiter = new Waiter(); //创建一个等待者实例
var first = function () {
  var dtd = waiter.Deferred();
  setTimeout(function () {
    dtd.resolve();
  }, 5000)
  return dtd; //返回监听这对象
}()
var second = function () { //第二个对象
  var dtd = waiter.Deferred();
  setTimeout(function () {
    dtd.resolve();
  }, 10000)
  return dtd;
}()
waiter.when(first, second).done(function () {
  console.log("success")
}, function () {
  console.log("success again")
}).fail(function () {
  console.log("fail")
})
  • JQuery中的Deferred对象
  • 异步请求的封装
  • 轮询