javaScript 设计模式与开发实践(二)

943 阅读7分钟

一、单例模式

1、定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。其核心是确保只有一个实例,并提供全局访问。

2、javaScript 中的单例模式

在javaScript中,经常会把全局变量当成单例来使用。但全局变量存在命名空间污染的问题,可以使用对象字面量的方式和使用闭包封装私有变量的方式进行优化。

3、惰性单例

比如页面创建div 的弹窗,不要在页面加载的时候创建它,而是在点击按钮的时候创建它(惰性)。如果多次点击按钮,则使用一个变量来判断是否已经创建,如果已经创建返回创建好的实例即可。

通用的惰性单例

    //惰性单例
const getSingle = function (fn){
    var result;
    return function (){
        return result || result =  fn.apply(this,arguments);
    }
}

惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。更奇妙的是,创建对象和管理单例的指责被分布到不同的方法中,这两个方法的组合让单例具有极大的魅力。

二、策略模式

1、定义

定义一系列算法,把它们一个个封装起来,并且使他们可以相互替换。目的是将算法的使用和实现分离开来。

    // 策略模式 
const starategies = {
    "S": function (salary){
        return salary * 4;
    },
    "A":function (salary){
        return salary * 3;
    },
    "B":function(salary){
        return salary * 2
    }
}
const calculateBonus = function (level,salary){
    return starategies[level](salary)
}
calculateBonus('S',1000)  ///40000

2、使用策略模式进行表单验证

// 使用策略模式进行表单验证
//创建策略对象
 const starategies = {
     idNonEmpty: function (value, errMsg){
        if(value === "") return errMsg;
     },
     minLength: function (value,length,errMsg){
         if(value.length < length) return errMsg;
     },
     isMobile: function (value,errMsg){
         if(!/(^1[3|5|8][0-9]{9}$))/.test(value)){
             return errMsg;
         }
     }
 };

 //Validator 类
let Validator = function (){
    this.catch = [];
}

Validator.prototype.add = function(dom,rules){
    var self = this;
    for(var i = 0, rule; rules[i++]){
        (function (rule){
            var strategyAry = rule.strategy.split(":");
            var errorMsg = rule.errorMsg;
            self.cache.push(function(){
                var strategy = strategyAry.shift();
                strategyAry.unshift(dom.value);
                strategyAry.push(errorMsg);
                return starategies[strategy].apply(dom, strategyAry);
            })
        })(rule)
    }
};

Validator.prototype.start = function (){
    for(var i = 0, validattorFunc, validatorFunc = this.catch[i++] ){
        var errorMsg = validatorFunc();
        if (errorMsg) return errorMsg ;
    }
};

三、代理模式

1、定义:

为一个对象提供一个代用品或占位符,以便控制对它的访问。

2、常用类型

(1)保护代理:用于控制不同权限的对象对目标对象的访问。但javaScript不容易实现。

(2)虚拟代理实现图片的预加载

 //虚拟代理合并请求
 var synchronousFile = function (id){
     console.log('开始同步文件')
 }

 var proxySynchronousFile = function(){
     var cache = []
     timer;
     return function(id){
        cache.push(id);
        if(timer) return;
        timer = setTimeout(function(){
            synchronousFile(cache.join(','));
            clearTimeout(timer);
            timer = null;
            cache.length = 0
        },2000)
     }
 }

 var checkbox = document.getElementsByTagName('input')
 for(var i = 0, c; c=checkbox[i++];){
    c.onclick = function(){
        if(this.checked === true){
            proxySynchronousFile(this.id)
        }
    }
 }

(3)缓存代理---缓存乘积模式

 var mult = function (){
    var a = 1;
    for(var i = 0,l = arguments.length; i < l; i++){
        a = a * arguments[i]
    }
    return a;
}

var createProxyFactory =  function(fn){
   var cache = {}
   return function(){
       var args =  Array.prototype.join.call(arguments,',');  
       if(args in cache){
           return cache[args];
       }
       return cache[args] = fn.apply(this, arguments);
   }

}
var proxyMult = createProxyFactory(mult)
console.log(proxyMult(3,4,5)); // 60

四、迭代器模式

1、含义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。大多数语言都已经内置的迭代器的实现。

2、自定义一个内部迭代器

const each = function (ary,callback){
    for(var i = 0, l = ary.length; i< l ; i++){
        callback.call(ary[i],i,ary[i])
    }
}
each([1,2,3],function(i,item){
    console.log(i,item);
    
})

3、实现外部的一个迭代器

const Iterator = function (obj){
    let current = 0;
    const next = function(){
        current += 1
    };
    const isDone = function (){
        return current >= obj.length
    };
    const getCurrItem =  function (){
        return obj[current]
    };
    return {
        next:next,
        isDone:isDone,
        getCurrItem:getCurrItem,
        length:obj.length
    }
}

const compare = function (iterator1,iterator2){
    if(iterator1.length !== iterator2.length) {  return console.log('不相等')};
    while (!iterator1.isDone() && !iterator2.isDone() ){
        if(iterator1.getCurrItem() !== iterator2.getCurrItem()){
            throw new Error('iterator1 和 iterator2 不相等' )
        }
        iterator1.next();
        iterator2.next();
    }
}

const iterator1 = Iterator([1,2,3])
const iterator2 = Iterator([1,4,3])
compare(iterator1,iterator2)

4、迭代器中止循环

//中止循环
const each = function (ary, callback){
    for(var i = 0,l = ary.length; i < l; i++;){
        if(callback (i,ary[i])) === false){
            break;
        }
    }
}
each([1,2,3,5,6,4,],function(i,n){
    if(n > 3){ return false }
})

五、发布-订阅模式

1、含义

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

2、发布订阅模式的通用实现

// 发布订阅功能放在单独的对象中
var event = {
    clientList:{},
    listen:function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
    },
    trigger:function(){
        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: function (key,fn) {
        var fns = this.clientList[key]
        if(!fns){
            return false
        }
        if(!fn){
            fns && (fns.length = 0)
        }else{
            for (var i = fns.length - 1; l>=0; l--){
                var _fn =  fns[l];
                if(_fn === fn){
                    fns.splice(l,1)
                }
            }
        }

    }

}
//定义一个installEvent函数,这个函数给所有对象都动态安装发布-订阅功能
var installEvent = function (obj){
    for(var i in event){
        obj[i] = event[i];
    }
}

var salesOffices = {}
installEvent(salesOffices)
salesOffices.listen('88',function(price){
    console.log(price);  
})
salesOffices.listen('120',function(price){
    console.log(price);  
})
salesOffices.trigger('88',2000)
salesOffices.trigger('120',4000)

3、必须先订阅后发布吗?

不一定。要满足先发布后订阅的能力。可以先将发布事件的动作的函数放入堆栈中,等到终于有人来订阅此事件的时候,可以循环便利这些事件。

4、优缺点

优点:时间和对象解隅,可以实现在异步编程当中,也可以在MVC和MVVM的架构中看到它的身影。

缺点:创建订阅者本身消耗一定的时间和内存,因为消息可能一直都没发生。同时,还会弱化对象之间的联系。

五、命令模式

1、定义

命令模式中的命令指的是执行某种特定事情的指令。最常见的场景是向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。

2、代码

var RefreshMenuBarCommand =  function ( receiver ){
    return {
        execute: function (){
            receiver.refresh();
        }        
    }
}

var setCommand = function (button,command) {
    button.onClick =  function (){
        command.execute();
    }
};

var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)

setCommand(button1,refreshMenuBarCommand)

六、组合模式

1、定义

组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构,同时,通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

2、代码示例


// file
var File = function(name){
    this.name = name
}
File.prototype.add = function(){
    throw new Error ('文件不能再添加文件')
}
File.prototype.scan = function (){
    console.log('文件' + this.name);     
}
// folder
var Folder =  function (name){
    this.name  = name
    this.files = []
};

Folder.prototype.add = function (file){
    this.files.push(file)
}

Folder.prototype.scan = function (){
    console.log('文件夹' + this.name); 
    for(var i = 0,file, files = this.files; file = files[i++];){
        file && file.scan();
    }
}

var folder = new Folder('学习资料')
var folder1 = new Folder('js')
var folder2 = new Folder('css')

var file1 = new File('js高级')
var file2 = new File('css')
var file3 = new File('html')

// 生成树结构
folder1.add(file1)
folder2.add(file2)
folder.add(folder1)
folder.add(folder2)
folder.add(file3)

// 添加原有树结构中
var folder3 =  new Folder ('语文')
var file4 = new File('一年级')
folder3.add(file4)
var file5 = new File('数学')
folder.add(folder3)
folder.add(file5)

folder.scan()

3、组合模式的优缺点

  • 优点 表示对象的部分和整体的机构层次,尤其是不确定这棵树到底存在多少层次的时候。可以通过顶点遍历整个对象。同时,组合模式可以可以让客户忽略组合对象和叶对象的区别,统一对待。
  • 缺点 每个对象看起来都差不多,难以分辨他们的区别。

七、模版方法模式

1、定义

模版方法是一种只需要继承就可以实现的非常简单的模式,一部分是抽象父类,一部分是具体实现的子类。通常在抽象父类封装了子类的算法框架,包括实现一些公共方法及封装了子类的方法的执行顺序。子类可以继承这个抽象类和算法结构,也可以重写父类的方法。

2、好莱坞原则

底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候,以何种方式去使用这些组件。作为子类,只负责实现一些设计上的细节。同时,因为javaScript实际没有提供真正的类继承,继承通过对象与对象的委托来实现的。下面这段代码,依然实现继承的效果。

// 好莱坞原则

var Beverage = function (param) {
    var boilWater = function () {
        console.log('把水煮沸');
    }
    var brew = param.brew || function () {
        throw new Error('必须传递brew 的方法')
    }

    var pourInCuP = param.pourInCuP || function () {
        throw new Error('必须传递pourInCuP 的方法')
    }
    var addCondiments = param.addCondiments || function {
        throw new Error('必须传递addCondiments的方法')
    }
    var F = function () { }
    F.prototype.init = function () {
        boilWater();
        brew();
        pourInCuP();
        addCondiments();
    }
    return F;

}


var conffee = Coffee({
    {
        brew:function(){
            console.log('用沸水冲咖啡');     
        },
        pourInCuP:function (){
            console.log('把咖啡倒进杯子');  
        },
        addCondiments:function () {
            console.log('加咖啡和牛奶');   
        },
    }
})

var coffee =  new Coffee()
coffee.init()

八、享元模式

1、定义:

是一种性能优化方案,当一个程序中使用了大量的相似对象,且造成很大的内存开销,剥离对象的外部状态,用较少的共享对象取代大量对象。(内部状态可以被共享,通常不会变化;外部状态不能共享,根据具体场景会变化)

2、对象池

对象池维护一个池子,如果需要对象的时候,不是直接new,而是转从对象池里面取,如果对象池里没有空闲的对象,则创建一个新的对象等待获取。

// 通用的对象池

var objectPoolFactory = function (createFn){
    var objectPool = [];
    return {
        create: function (){
            var obj = objectPool.length === 0? createFn.apply(this,arguments) || objectPool.shift()
            return obj
        },
        recover:function (obj){
            objectPool.push(obj)
        }
    }
}

九、职责链模式

1、定义

使多个对象都有机会处理请求,从而避免请求的发送和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有个对象处理它为止。

var chain  =  function (fn){
    this.fn = fn;
    this.successor = null;
}
chain.prototype.setNextSuccessor = function(successor){
    return this.successor = successor
}
chain.prototype.passRequest = function (){
    var ret = this.fn.applythis,arguments);
    if(ret === "nextSuccessor"){
        return this.successor && this.successor.passRequest.apply(this.successor,arguments)
    }

    return ret;
}
chain.prototype.next = function (){
    return this.successor && this.successor.passRequest.apply(this.successor,arguments)
}

十、其他

1、 单一职责原则

一个对象(方法)只做一件事情。比如:代理模式,迭代器模式,单例模式,和装饰者模式都有许多应用。但并不是所有职责都应该一一分离。比如两个职责总是同时变化,和必须确定他们会发生才会有意义。

SRP原则的优点有助于代码的复用和单元测试,但另外一面,会增加代码的复杂度。各个代 码之间的联系变复杂。

2、最少知识原则

LKP指的是一个软件实体应当尽可能少的与其他实体发生相互的作用。比如:中介者模式和外观模式。封装内部数据,对外只暴露一个接口,也是该原则的体现。

3、开放、封闭原则

4、代码重构

  • 将大块的代码,提炼部分函数
  • 合并重复的条件片段
  • 把复杂的条件分支语句提炼成函数
  • 合理使用循环
  • 提前退出函数嵌套条件语句,将外层if表达式进行反转
  • 传递对象参数代替过长的参数列表
  • 用return 退出多层循环