javascript设计模式

350 阅读5分钟

前言

本文介绍几种常用的前端设计模式,主要内容均来自《JavaScript设计模式与开发实践》,学习前需要熟悉闭包、原型链、this、作用域等前端基础知识。

单例模式

定义

保证一个类只有一个实例,并提供一个访问他的全局访问点。

代码实现

const Singleton = function(name) {
    this.name = name;
}
Singleton.prototype.getName = function() {
    console.log(this.name);
}
Singleton.getInstance = (function(){
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    } 
})();
const a = Singleton.getInstance('a');
const b = Singleton.getInstance('b');
console.log(a === b); // true

惰性单例

const getSingle = function(fn) {
    var result = null;
    return function(){
        return result || (result = fn.apply(this, arguments))
    }
}
var createSingleIframe = getSingle(function(){
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    return iframe;
})
document.getElementById('loginBtn').onclick = function(){
    var loginLayer = createSingleIframe();
    loginLayer.src= 'http://www.baidu.com'
}

小结

单例模式是非常实用的一种模式,特别是惰性单例模式,在合适的时候才创建,并且只创建唯一的一个。更奇妙的是,惰性单例将创建对象和管理单例的职责分配到不同的方法中,这2个方法组合起来才具有单例模式的威力。

策略模式

定义

定义一系列的方法,把它们一个个封装起来,并且使它们可以互相替换。

实现

const strategies = {
    'S': function(salary){
        return salary * 4
    },
    'A': function(salary){
        return salary * 3
    }
}
const calculateBonus = function(level,salary){
    return strategies[level](salary);
}
console.log(calculateBonus('S',1000)) // 4000
console.log(calculateBonus('A',1000)) // 3000

小结

策略模式利用多态、组合和委托等技术和思想,可以有效避免多重条件语句,避免许多重复性工作,可以用在表单校验、计算工资、旅行路线等常用问题上

代理模式

定义

为一个对象提供一个代用品或者占位符,以便控制对他的访问,主要分为保护代理与虚拟代理,虚拟代理把一些开销很大的对象,延迟到需要使用时候才去创建,保护代理用于控制不同权限的对象对目标对象的访问,因在javascript中不易实现,因为我们无法判断谁访问了对象,下文主要介绍虚拟代理。

虚拟代理实现图片预加载

const myImage = (function(){
    const imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src){
            imgNode.src = src;
        }
    }
})()
var proxyImage = (function(){
    var img =new Image();
    img.onload=function(){
        myImage.setSrc(this.src)
    }
    return {
        setSrc:function(src){
            myImage.setSrc('https://img.alicdn.com/tfs/TB1gRhGQVXXXXb.XVXXXXXXXXXX-15-15.gif');
            img.src = src;
        }
    }
})()
proxyImage.setSrc('http://wap.autostreets.com/img/title.d0bc5df8.png')

小结

代理模式体现了单一职责原则,开发中常用的主要是虚拟代理与缓存代理,当发现不易直接访问某个对象时候可以考虑使用代理模式。

发布-订阅模式

定义

他定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都将得到通知。

实现

var Event = (function(){
    var global = this,Event,_default = 'default';
    Event = function(){
        var _listen,
             _trigger,
             _remove,
             _shift = Array.prototype.shift,
             _unshift = Array.prototype.unshift,
             namespaceCache = {},
             _create,
             each = function(ary,fn) {
                 var ret;
                 for(let i =0,l = ary.length; i<l; i++) {
                     const n = ary[i];
                     ret = fn.call(n,i,n);
                 }
                 return ret;
             };
             _listen = function(key,fn,cache){
                 if(!cache[key]){
                     cache[key] = []
                 }
                 cache[key].push(fn);
             }
             _remove = function(key,fn,cache){
                 if(cache[key]){
                     if(fn){
                        for(let i= cache[key].length; i>=0;i--){
                            if(cache[key][i] === fn) {
                                cache[key].splice(i,1);
                            }
                        }
                     }else{ 
                         cache[key] = [];
                     }
                 }
             };
             _trigger = function(){
                 const cache = _shift.call(arguments),
                        key = _shift.call(arguments),
                        args = arguments,
                        _self = this,
                        stack = cache[key];
                if(!stack || !stack.length){
                    return;
                }
                return each(stack,function(){
                    return this.apply(_self,args)
                })
             };
             _create = function(nameSpace){
                 var nameSpace = nameSpace || _default;
                 var cache = {},
                    offlineStack = [],
                    ret ={
                        listen: function(key,fn,last) {
                            _listen(key,fn,cache);
                            if(offlineStack === null) {
                                return
                            }
                            if(last === 'last') {
                                offlineStack.length && offlineStack.pop()();
                            }else{
                                each(offlineStack,function(){
                                    this();
                                })
                            }
                            offlineStack = null
                        },
                        one: function(key,fn,last){
                            _remove(key,cache);
                            this.listen(key,fn,last);
                        },
                        remove: function(key,fn){
                            _remove(key,cache,fn);
                        },
                        trigger:function(){
                            var fn,
                                args,
                                _self = this;
                            _unshift.call(arguments,cache);
                            args =arguments;
                            fn =function(){
                                return _trigger.apply(_self,args);
                            }
                            if(offlineStack){
                                return offlineStack.push(fn);
                            }
                            return fn();
                        }
                    }
                    return nameSpace ? (namespaceCache[nameSpace] ? namespaceCache[nameSpace] : namespaceCache[nameSpace] = ret) : ret
             };
            return {
                create: _create,
                one: function(key,fn,last) {
                    const event = this.create();
                    event.one(key,fn,last);
                },
                remove:function(key,fn){
                    const event = this.create();
                    event.remove(key,fn);
                },
                listen: function(key,fn,last){
                    const event = this.create();
                    event.listen(key,fn,last);
                },
                trigger:function(){
                    const event = this.create();
                    event.trigger.apply(this,arguments);
                }
            }
    }()
    return Event;
})()

Event.listen('click',function(a){
    console.log(a)
})
Event.trigger('click',1);

小结

发布-订阅模式可以广泛应用于异步编程中,比如订阅ajax中的success、error事件,还可以取代对象之间硬编码的通知机制,一个对象不再显式的调用另外一个对象的接口。

命令模式

定义

将一个请求封装为一个对象,从而使我们可以用不同的请求对 客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作

命令模式实现菜单程序

const button1 = document.getElementById('btn1');
const button2 = document.getElementById('btn2');
const button3 = document.getElementById('btn3');

const setCommand = function(button,command) {
    button.onclick=function(){
        command.execute();
    }
}
const MenuBar = {
    refresh: function(){
        console.log('刷新菜单目录')
    }
}
const SubMenu = {
    add: function(){
        console.log('增加子菜单');
    },
    del: function(){
        console.log('删除子菜单')
    }
}
const RefreshMenuBarCommand = function(receiver){
    this.receiver = receiver;
}
RefreshMenuBarCommand.prototype.execute = function(){
    this.receiver.refresh();
}
const AddSubMenuCommand = function(receiver){
    this.receiver = receiver;
}
AddSubMenuCommand.prototype.execute = function(){
    this.receiver.add();
}
const DelSubMenuCommand = function(receiver){
    this.receiver = receiver;
}
DelSubMenuCommand.prototype.execute = function(){
    console.log('删除子菜单')
}
const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
const addSunMenuCommand = new AddSubMenuCommand(SubMenu);
const delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand(button1, refreshMenuBarCommand); // 刷新菜单目录
setCommand(button2, addSunMenuCommand); // 增加子菜单
setCommand(button3, delSubMenuCommand); // 删除子菜单

小结

命令模式主要应用于不知道请求的接收者是谁,也不知道具体执行什么操作,此时希望用一种松耦合的方式设计程序,此时使得请求发送者和接收者能消除彼此直接的耦合关系

状态模式

定义

允许一个对象在状态改变时改变他的行为,对象看起来似乎修改了他的类。

实现

const OffLightState = function(light) {
    this.light = light;
}
OffLightState.prototype.buttonWasPressed = function() {
    console.log('弱光');
    this.light.setState(this.light.weakLightState)
}

const WeakLightState = function(light) {
    this.light = light;
}
WeakLightState.prototype.buttonWasPressed = function() {
    console.log('强光');
    this.light.setState(this.light.strongLightState)
}
const StrongLightState = function(light) {
    this.light = light;
}
StrongLightState.prototype.buttonWasPressed = function() {
    console.log('关灯');
    this.light.setState(this.light.offLightState)
}
const Light = function() {
    this.offLightState = new OffLightState(this);
    this.weakLightState = new WeakLightState(this);
    this.strongLightState = new StrongLightState(this);
    this.button = null;
}
Light.prototype.init = function(){
    const button = document.createElement('button'),
          self = this;
    this.button = document.body.appendChild(button);
    this.button.innerHTML = '开关';
    this.currState = this.offLightState;
    this.button.onclick = function(){
        self.currState.buttonWasPressed();
    }
}
Light.prototype.setState = function(newState) {
    this.currState = newState
}
const light = new Light();
light.init();

小结

状态模式定义了状态与行为之间的关系,并将他们封装在一个类里。避免了Context无限膨胀,去掉过多的条件判断,可以使杂乱无章的代码变得清晰。

装饰者模式

定义

给对象动态的增加职责,能够在不改变对象自身的基础上,在运行时动态的给对象增加职责

装饰者模式实现表单验证

Function.prototype.before = function(beforefn){
    const _self = this;
    return function() {
        if(beforefn.apply(this, arguments) === false) {
            return
        }
        return _self.apply(this, arguments);
    }
}
const validata = function(){
    if(unsename.value === '') {
        alert('用户名不能为空');
        return false
    }
    if(password.value === '') {
        alert('密码不能为空');
        return false
    }
}
const formSubmit = function(){
    const param = {
        password: password.value,
        username: username.value
    } 
    ajax('http://XXX/login',param)
}
formSubmit = formSubmit.before(validata);
submitBtn.onclick=function(){
    formSubmit();
}

小结

装饰者模式用于一开始不能确定对象的全部功能,为对象动态加入。作为框架作者,我们希望框架内部提供的是一些稳定的功能,而那些个性化的功能可以在框架之外动态增加上去,这可以避免为了让框架拥有更多的功能,而去写很多if,else预测用户的实际需要。