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 使用场景:

  • 有时候需要向某些对象发送请求,但是并不知道请求的接收 者是谁,也不知道被请求的操作是什么

1.3 实现发布—订阅模式的步骤


朦胧之月
3 声望1 粉丝

爱编程、爱生活