1. 单例模式
1.1 定义:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1.2 使用场景:
- 只需要一个的对象,比如线程池、全局缓存、浏览器中的window对象等
- 登录浮窗只会被创建一次
1.3 代码实现:
// 单例类
var CreateDiv = function(html){
this.html = html;
this.init();
};
CreateDiv.prototype.init = function(){
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
// 代理类 管理单例
var ProxySingletonCreateDiv = (function(){
var instance;
return function(html){
instance = new CreateDiv(html);
}
return instance;
})();
// 使用实例
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
console.log(a === b);
1.4 惰性单例:
// 获取实例的方法
var getSingle = function(fn){
var result;
return function(){
return result || (result = fn.apply(this, arguments));
}
}
// 创建实例的方法
var createLoginLayer = function(){
var div = document.createElement('div');
div.innerHTML = '登录浮窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
// 获取实例
var createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
}
2. 策略模式
1.1 定义:
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成:
- 第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程
- 第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策类
1.2 使用场景:
- 封装算法,例如:根据员工的工资基数和年底绩效情况来发放的年终奖
- 封装业务规则,例如:表单的校验
1.3 根据员工的工资基数和年底绩效情况来发放的年终奖,代码实现:
// 最初代码
var calculateBonus = function(performanceLevel, salary){
if(performanceLevel === 'S'){
return salary * 4;
}
if(performanceLevel === 'A'){
return salary * 3;
}
if(performanceLevel === 'B'){
return salary * 2;
}
}
使用策略模式重构
// 策略模式重构
var performanceS = function(){
return salary * 4;
}
var performanceA = function(){
return salary * 3;
}
var performanceB = function(){
return salary * 2;
}
var calculateBonus = function(func, salary){
return func(salary);
}
console.log(calculateBonus(performanceS, 10000));
JavaScript版本的策略模式
// 校验逻辑封装成策略对象
var strategies = {
"S": function(salary){
return salary * 4;
},
"A": function(salary){
return salary * 3;
},
"B": function(salary){
return salary * 2;
}
};
var calculateBonus = function(level, salary){
return strategies[level](salary);
}
console.log(calculateBonus('S', 2000));
1.4 表单校验,代码实现:
- 用户名不能为空
- 密码长度不能少于6位
- 手机号码必须符合格式
// 最初代码
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function(){
if(registerForm.userName.value === ''){
alert('用户名不能为空');
return false;
}
if(registerForm.password.value.length < 6){
alert('密码长度不能少于6位');
return false;
}
if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)){
alert('手机号码格式不正确');
return false;
}
}
使用策略模式重构
// 使用策略模式重构
// 校验逻辑封装成策略对象
var strategies = {
isNonEmpty: function(value, errorMsg){ // 不能为空
if(value === ''){
return errorMsg;
}
},
minLength: function(value, length, errorMsg){ // 限制最小长度
if(value.length < length){
return errorMsg;
}
},
isMobile: function(value, errorMsg){ // 手机号码格式
if(!/(^1[3|5|8[0-9]{9}$])/.test(value)){
return errorMsg;
}
}
};
// 环境类
// context类
class Validator {
constructor(){
this.cache = []; // 保存校验规则
}
add(dom, rules){
var self = this;
for(var i = 0, rule; rule = rules[i++];){
(function(rule){
var ary = rule.strategy.split(':'); // 把strategy和参数分开
var errrorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = ary.shift(); // 用户挑选的strategy
ary.unshift(dom.value); // 把input的value添加进参数列表
ary.push(errorMsg); // errorMsg添加进参数列表
return strategies[strategy].apply(dom, ary);
});
})(rule)
}
}
start(){
for(var i = 0, validatorFunc; validatorFunc = this.cache[i++];){
var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
if(msg){ // 如果有确切的返回值,说明未通过校验
return msg;
}
}
}
}
var validataFunc = function(){
var validator = new Validator();
/* 添加检验规则 */
validator.add(registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用户名不能为空'}]);
validator.add(registerForm.password, [{ strategy: 'minLength', errorMsg: '密码长度不能少于6位'}]);
validator.add(registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: '手机号码格式不正确'}]);
var errorMsg = validator.start(); // 获取校验结果
return errorMsg; // 返回校验结果
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function(){
var errorMsg = validataFunc(); // errorMsg有确切的返回值,说明未通过校验
if(errorMsg){
alert();
return false; // 阻止表单提交
}
}
3. 代理模式
1.1 定义:
为一个对象提供一个代用品或占位符,以便控制对它的访问
1.2 使用场景:
- 图片过大或网络不佳时,需要图片预加载
- 虚拟代理合并HTTP请求
- 缓存代理用于计算乘积
- 缓存代理用于ajax异步请求数据
1.3 虚拟代理实现图片预加载
// 最初代码
var MyImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
var img = new Image;
img.onload = function(){
imgNode.src = img.src;
}
return {
setSrc: function(src){
imgNode.src = 'loading.gif';
img.src = src;
}
}
})();
MyImage.setSrc('http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');
使用虚拟代理模式重构
var myImage = (function(){
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function(src){
imgNode.src = src;
}
})();
// 虚拟代理
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage(this.src);
}
return function(src){
myImage('loading.gif');
img.src = src;
}
})();
proxyImage('http://imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');
1.3 虚拟代理合并HTTP请求
假设我们在做一个文件同步的功能,当我们选中一个 checkbox 的时候,它对应的文件就会被同步到另一台备用服务器上。频繁的网络请求将会带来相当大的开销。
解决方案是:
我们可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求, 最后一次性发送给服务器。
var synchronousFile = function(id){
cosole.log('开始同步文件,id为:' + id);
}
// 虚拟代理
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的ID
timer; // 定时器
return function(id){
cache.push(id);
if(timer){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile(cache.join(','));
clearTimeout(timer);
timer = null;
cache.length = 0; // 清空ID集合
}, 2000);
}
})();
var checkbox = document.getElement.getElementsByTagName('input');
for(var i = 0, c; c = checkbox[i++];){
c.onclick = function(){
if(this.checked === true){
proxySynchronousFile(this.id);
}
}
}
1.4 缓存代理用于计算乘积
/*计算乘积*/
var mult = function(){
var a = 1;
for(var i = 0, l = arguments.lenght; i < l; i++){
a = a * arguments[i];
}
returna;
}
/*创建缓存代理的工厂*/
var createProxyFactory = function(fn){
var cache = {};
return function(){
var args = Array.protorype.join.call(arguments, ',');
if(args in cache){
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
var proxyMult = createProxyFactory(plus);
console.log(proxyMult(1, 2, 3, 4, 5));
}
4. 迭代器模式
1.1 定义:
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
1.2 使用场景:
- 把迭代的过程从业务逻辑中分离出来
- 在不同的浏览器环境下,选择的上传方式是不一样的
1.3 实现自己的迭代器
var each = function(ary, callback){
for(var i = 0, l = arg.length; i < l; i++){
callback.call(ary[i], i, ary[i]); // 把下标和元素作为参数传给callback函数
}
};
each([1, 2, 3], function(i, n){
console.log([i, n]);
})
1.4 迭代器模式在上传文件里的应用
var getActiveUploadObj = function(){
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload");
} catch(e) {
return false;
}
}
var getFlashUploadObj = function(){
if ( supportFlash() ){ // supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $( str ).appendTo( $('body') );
}
}
var getFormUpladObj = function(){
var str = '<input name="file" type="file" class="ui-file"/>'; // 表单上传
return $( str ).appendTo( $('body') );
};
var iteratorUploadObj = function(){
for ( var i = 0, fn; fn = arguments[ i++ ]; ){
var uploadObj = fn();
if ( uploadObj !== false ){
return uploadObj;
}
}
};
var uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );
5. 发布-订阅模式(观察者模式)
1.1 定义:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
一为时间上的解耦, 二为对象之间的解耦
1.2 使用场景:
- 发布—订阅模式可以广泛应用于异步编程中
- 订阅 ajax 请求的 error、succ 等事件
- 如果想在动画的每一帧完成之后做一些事情,那我们可以订阅一个事件,然后在动画的每一帧完成之后发布这个事件
1.3 实现发布—订阅模式的步骤
- 首先要指定好谁充当发布者
- 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者
- 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数
1.4 发布—订阅模式的通用实现
class Event {
constructor(){
this.clientList = [];
}
listen(key, fn) {
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
}
trigger(){
var key = Array.prototype.shift.call(arguments),
fns = this.clientList[key];
if(!fns || fns.length === 0){ // 如果没有绑定对应的消息
return false;
}
for(var i = 0, fn; fn = fns[i++];){
fn.apply(this, arguments);
}
}
remove(key, fn){
var fns = this.clientList[key];
if(!fns){ // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
fns && (fns.length = 0);
} else {
for(var l = fns.length - 1; l >= 0; l--){ // 反向遍历订阅的回调函数列表
var _fn = fns[l];
if(_fn === fn){
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
}
}
var salesOffices = new Event();
salesOffices.listen('squareMeter88', fn1 = function(price){ // 小明订阅消息
console.log('价格=' + price);
});
salesOffices.listen('squareMeter100', function(price){ // 小红订阅消息
console.log('价格=' + price * 2);
});
salesOffices.remove('squareMeter88', fn1);
salesOffices.trigger('squareMeter100', 10000);
6. 命令模式
1.1 定义:
指的是一个执行某些特定事情的指令
1.2 使用场景:
- 有时候需要向某些对象发送请求,但是并不知道请求的接收 者是谁,也不知道被请求的操作是什么
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。