前言
本文介绍几种常用的前端设计模式,主要内容均来自《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预测用户的实际需要。