转载自:JavaScript设计模式
之前看了 JavaScript 中常见设计模式整理 这篇文章,这里我也把平时整体的设计模式分享一下。
设计模式是解决一类问题的模板,为软件设计中常见的问题提供解决方案。JavaScript 是一种弱类型、动态的、基于原型的语言,所以它可以以很简单的方式去实现一些模式。切记不要去套用后台语言的设计模式,这往往会丢失 JavaScript 这门语言的动态性和灵活性。
一些常用的设计模式
- 单例模式:保证一个对象只有一个实例,第二次创建的实例和第一次创建的完全一样。
//单例模式
//一个类只能构造出唯一实例
//静态属性方式实现
function Person() {
if (typeof Person.instance === 'object') {
return Person.instance;
}
this.name = 'vector';
this.age = 25;
Person.instance = this;
}
//闭包方式实现
var Person = (function () {
var instance;
return function (name, age) {
if (instance) {
return instance;
}
this.name = 'vector';
this.age = 25;
instance = this;
};
}());
var p1 = new Person();
var p2 = new Person();
console.log(p1 === p2); //true
- 工厂模式:提供创建对象的方法。
//工厂模式
//工厂模式用于生产对象
function factory(name, age) {
var obj = {};
obj.name = name;
obj.age = age;
obj.sayName = function () {
console.log(this.name);
};
obj.sayAge = function () {
console.log(this.age);
};
return obj;
}
var p1 = factory('vector', 25);
console.log(p1); //{name: "vector", age: 25, sayName: ƒ, sayAge: ƒ}
- 迭代器模式:为遍历一个数据结构提供方法。
//迭代器模式
//遍历某一个数据结构,这里以数组为例
var iterator = (function () {
var index = 0,
data = [1, 2, 3, 4, 5],
len = data.length;
return {
next: function () {
if (!this.hasNext()) {
return null;
}
return data[index++];
},
hasNext: function () {
return index < len;
}
};
}());
while(iterator.hasNext()) {
console.log(iterator.next());
}
- 装饰者模式:增强普通对象的功能,按照顺序进行装饰。
//装饰者模式
//增加普通对象的功能
function Sale(price) {
this.price = price;
this.decorators_list = [];
}
Sale.decorators = {};
Sale.decorators.fedtax = {
getPrice: function (price) {
return price + price * 5 / 100;
}
};
Sale.decorators.money = {
getPrice: function (price) {
return '$' + price.toFixed(2);
}
};
Sale.prototype.decorate = function (decorator) {
this.decorators_list.push(decorator);
};
Sale.prototype.getPrice = function () {
var price = this.price,
i,
len = this.decorators_list.length,
name;
for (i = 0; i < len; i++) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}
return price;
};
var sale = new Sale(100);
sale.decorate('fedtax');
sale.getPrice(); //105
sale.decorate('money');
sale.getPrice(); //$105.00
- 策略模式:根据不同命令命中不同算法,可以避免使用多重条件语句。
//策略模式
//根据不同命令可以命中不同的算法
var operate = {
add: function (a, b) {
return a + b;
},
sub: function (a, b) {
return a - b;
},
mul: function (a, b) {
return a * b;
},
div: function (a, b) {
return a / b;
}
};
var calc = function (cmd, arg1, arg2) {
return operate[cmd](arg1, arg2);
};
calc('add', 1, 3); //4
calc('mul', 2, 4); //8
- 外观模式:将复杂的子系统功能隐藏在外观之后,提供简单的调用接口。
//外观模式
//为复杂的子系统功能提供简单的调用接口
//在重构工作和写兼容代码中很有帮助
var myevent = {
//阻止冒泡和默认事件
stop: function (e) {
if (typeof e.preventDefault === 'function') {
e.preventDefault();
}
if (typeof e.stopPropagation === 'function') {
e.stopPropagation();
}
//IE
if (typeof e.returnValue === 'boolean') {
e.returnValue = false;
}
if (typeof e.cancelBubble === 'boolean') {
e.cancelBubble = true;
}
}
};
- 代理模式:通过包装一个对象以控制对它的访问,ES6中的Proxy就是通过代理扩展对象功能。
//代理模式
//通过包装一个对象以控制对它的访问
//以缓存代理为例
const requestResult = async function (id) {
let requestConfig = {
credentials: 'include',
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: "cors",
cache: "force-cache"
};
let url = '/service/v1/' + id;
const response = await fetch(url, requestConfig);
const responseJson = await response.json();
return responseJson;
};
const proxy = {
cache: {},
request: async function (id) {
if (this.cache[id]) {
//直接取缓存内容
return this.cache[id];
} else {
return requestResult(id);
}
}
};
proxy.request(1);
proxy.request(1); //从缓存获取
- 中介者模式:对象之间不直接通信,借助中介对象进行通信,解决对象间通信产生的紧耦合问题。
//中介者模式
//独立的对象之间不直接通信,借助中介对象,当其中一个独立对象状态改变后,通知中介对象,中介对象也会将该变化通知到其他应该知道此变化的对象
//以两个玩家玩游戏为例,玩家play1按按键1,玩家play2按按键2,30s内看谁按得次数最多,并且分数实时显示在计分板
//玩家构造函数
function Player (name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function () {
this.points++;
mediator.played();
};
//计分板对象
var scoreboard = {
element: document.getElementById('result'),
//更新得分显示
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += '<p><strong>' + i + '<\/strong>: ';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
}
//中介对象,作为play1、play2、计分板、按键事件之间通信中介
var mediator = {
//所有的玩家
players: {},
//初始化
setup: function () {
var players = this.players;
players.play1 = new Player('play1');
players.play2 = new Player('play2');
},
//如果有人played,则更新分值
played: function () {
var players = this.players;
var score = {
play1: players.play1.points,
play2: players.play2.points
};
scoreboard.update(score);
},
//处理用户交互
keypress: function (e) {
e = e || window.event;
if (e.which == 49) {
//按键1
mediator.players.play1.play();
return;
} else if (e.which == 50) {
//按键2
mediator.players.play2.play();
return;
}
}
};
//运行游戏
mediator.setup();
window.onkeypress = mediator.keypress;
setTimeout(function () {
window.keypress = null;
alert('game over!');
}, 30000);
- 观察者模式:又称发布-订阅模式,发布者中保存着一份订阅者的列表,当发布者的状态发生改变的时候就主动向订阅者发出通知。
//观察者模式
//当发生一个感兴趣的事件时,将该事件通告给所有观察者
//形成对象之间的松散耦合
function EventTarget() {
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function (type, handler) {
if (typeof this.handlers[type] == "undefined") {
this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire: function (event) {
if (!event.target) {
event.target = this;
}
if (this.handlers[event.type] instanceof Array) {
var handlers = this.handlers[event.type];
for (var i = 0, len = handlers.length; i < len; i++) {
handlers[i](event);
}
}
},
removeHandler: function (type, handler) {
if (this.handlers[type] instanceof Array) {
var handlers = this.handlers[type];
for (var i = 0, len = handlers.length; i < len; i++) {
if (handlers[i] === handler) {
break;
}
}
handlers.splice(i, 1);
}
}
};
var publisher = new EventTarget(); //定义发布者
//向发布者中注册订阅者
publisher.addHandler('call1',function () {
console.log('call1');
});
publisher.addHandler('call1',function () {
console.log('call1 again');
});
//发布者发布消息
publisher.fire({type: 'call1'}); //call1 call1 again
- 事件委托模式:在定义DOM事件时用的最多,在父元素上监听事件代替给众多子元素监听事件。
//事件委托模式
//给每一个子元素注册监听器比较耗内存,基于DOM事件冒泡的原理,将事件监听器注册在父元素上。
//给ul下的li绑定点击事件
var ul = document.getElementById('list');
ul.addEventListener('click', function (e) {
var target = e.target;
if (target.node === 'LI') {
//处理逻辑
}
});