前言
在日常编写JS代码的过程中,运用一定的设计模式可以让我们的代码更加优雅,灵活。
工厂模式
工厂就是大批量的产生相似产品的地方。那简单的工厂模式就可以理解为解决多个相似的问题。
// 工厂函数
function Person(name, age) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.say = function () {
console.log(this.name);
};
return obj;
}
person1 = new Person('Joke', 25);
person2 = new Person('Nike',18);
person1.say(); // Joke
person2.say(); // Nike
优点
能解决多个相似问题
缺点
工厂函数太僵化,创建实例不能对其扩展,无法实现实例对象的个性化
优化
强类型语言里面有个名词抽象类,抽象类就是这个类只能被继承,不能实例化,我们现在就把这个工厂函数包装成一个抽象类,然后这个类实现基本属性的初始化,其他个性的东西就等子类继承它的时候另外扩展。
// 父类(抽象类)
function BicycleShop(name) {
this.name = name;
}
BicycleShop.prototype = {
constructor: this,
showName() {
return this.name;
},
sellBicycle() {
let bicycle = this.createBicycle();
bicycle.A();
bicycle.B();
return bicycle;
},
createBicycle() {
throw new Error("父类是抽象类不能直接调用,需要子类重写方法");
}
};
// 继承函数
function extend(Sub,Sup) {
let F = new Function();
F.prototype = Sup.prototype;
Sub.prototype = new F();
Sub.prototype.constructor = Sub;
}
// 子类
function BicycleChild(name) {
// 初始父类属性
BicycleShop.call(this,name);
}
// 继承父类
extend(BicycleChild,BicycleShop);
// 实例化一个子类对象,测试是否可以直接使用createBicycle方法
let child1 = new BicycleChild('fenghuang');
child1.sellBicycle(); // 抛出异常
// 重写父类的createBicycle方法
BicycleChild.prototype.createBicycle = function() {
let A = function () {
console.log("执行业务A");
};
let B = function () {
console.log("执行业务B");
};
return {
A: A,
B: B
}
};
// 再实例化一个子类对象
let child2 = new BicycleChild('fenghuang');
child2.sellBicycle(); // 执行A,B函数
单体模式
单体模式的特点就是:只能被实例化一次
例如,创建弹窗,如果弹窗不存在,新建;弹窗存在,直接返回存在的弹窗
function CreateDialog(html) {
this.html = html;
this.init();
}
CreateDialog.prototype.init = function () {
let div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
let Dialog = (function () {
// 闭包实现变量缓存
let instance;
return function (html) {
// 如果instance为null,则实例化CreateDialog,否则,直接返回
if(!instance) {
instance = new CreateDialog(html);
}
return instance;
}
})();
let a = Dialog("aaa");
let b = Dialog("bbb");
console.log(a===b); // true
优点
只能被实体化一次,特别适合新增dom元素的操作
代理模式
例如,异地恋的情侣,男生想给女生送杯奶茶喝,但是他自己无法做到,这时候就得求助美团,让美团代自己把奶茶送给自己的女朋友,这就是代理模式。
function Girlfriend(name) {
this.name = name;
}
function Boyfriend(girl) {
this.girl = girl;
this.showLove = function (gift) {
console.log("Hi" + this.girl.name + ", 你的男朋友送了你" + gift);
}
}
function Meituan(girl) {
this.girl = girl;
this.send = function (gift) {
(new Boyfriend(this.girl)).showLove(gift);
}
}
let mt = new Meituan(new Girlfriend("小雪"));
mt.send("奶茶"); // Hi小雪, 你的男朋友送了你奶茶
优点
男生只需要关注结果,中间买奶茶,送奶茶的过程不需要关心
职责链模式
例如,工地上有老板,有白、红、黄帽子,老板派一个任务,如果白帽子执行不了就把任务传递给红帽子,红帽子执行不了就传递给黄帽子,这样就形成了一个职责链条。
function bigBoss(task) {
console.log("谁来"+ task);
return "nextExecutor";
}
function whiteHat(task) {
if(task === "泡澡") {
console.log("泡澡这种事就来找我");
}else {
console.log("我戴的是白帽子,我只喜欢泡澡!");
return "nextExecutor";
}
}
function redHat(task) {
if(task === "喝酒") {
console.log("喝酒这种事就来找我");
}else {
console.log("我戴的是红帽子,我只喜欢喝酒!");
return "nextExecutor";
}
}
function yellowHat(task) {
console.log("我来"+task);
}
function Build(fn) {
this.fn = fn;
this.executor = null;
}
// 设置下一级
Build.prototype.setNextExecutor = function (executor) {
return this.executor = executor;
};
// 给下一级派发任务
Build.prototype.dispatchTask = function() {
let ret = this.fn.apply(this, arguments);
if(ret === "nextExecutor") {
return this.executor && this.executor.dispatchTask.apply(this.executor, arguments);
}
return ret;
};
let boss = new Build(bigBoss);
let white = new Build(whiteHat);
let red = new Build(redHat);
let yellow = new Build(yellowHat);
boss.setNextExecutor(white);
white.setNextExecutor(red);
red.setNextExecutor(yellow);
boss.dispatchTask("泡澡");
boss.dispatchTask("喝酒");
boss.dispatchTask("盖房子");
优点
- 解耦,发送者只需要把请求发给第一个接收者即可
- 职责链中增加一个接收者或者删除一个接收者都比较容易
命令模式
这个模式其实在开发中经常用到,比如我们给一个按钮添加了一个click事件,点我们触发这个click事件的时候,其实就是执行了命令。
let btn = document.querySelector('#btn');
btn.addEventListener('click',function (e) {
console.log("按钮事件被触发");
},false);
btn.click();
依据上面的代码,我们就可以生成如下的命令模式
// 创建命令内容1,2,3
let commmand1 = {
content: function() {
console.log('命令1');
}
};
let commmand2 = {
content: function() {
console.log('命令2');
}
};
let commmand3 = {
content:function() {
console.log('命令3');
}
};
// 宏命令,包含命令的添加方法和执行方法
let command = function () {
return {
commandList: [],
add(command) {
this.commandList.push(command);
},
excute() {
for(let item of this.commandList) {
item.content();
}
}
}
};
// 发布命令的对象
let c = command();
// 添加命令
c.add(commmand1);
c.add(commmand2);
c.add(commmand3);
// 执行命令
c.excute();
优点
一个指令可以执行多个命令
策略模式
《孙子兵法》讲的就是策略,根据不同的形势制定相应的运动方针,我们就用代码来实现孙子兵法
function sunzi(situation) {
if (situation === 'A') {
console.log("A形势,执行三十六计之瞒天过海")
}
if (situation === 'B') {
console.log("B形势,执行三十六计之围魏救赵")
}
if (situation === 'C') {
console.log("C形势,执行三十六计之借刀杀人")
}
// ...剩余33计
}
sunzi('A');
这就是策略模式,但是代码中有大量的if判断,非常损耗性能,所以需要优化一下
let optimizeSunzi = {
A() {
console.log("A形势,执行三十六计之瞒天过海");
},
B() {
console.log("B形势,执行三十六计之围魏救赵")
},
C() {
console.log("C形势,执行三十六计之借刀杀人")
}
};
let getHelp = function (situation) {
return optimizeSunzi[situation]();
};
getHelp('B');
优点
代码更容易理解,也更容易扩展,同时代码也可以复用
发布订阅模式
京东上有一款鞋子处于缺货状态,小红和小绿都看中了它,但是她俩不知道到底什么时候才有货,不然得无时无刻的去京东上看,此时京东有一个功能到货通知,如果小红和小绿都启用了这个功能,一旦这个鞋子有货了就会发信息给她俩,这就是发布订阅模式。京东是发布者,小红和小绿是订阅者。
let publish = (function () {
// 订阅者容器
let subArr = [];
return {
add(fn) {
subArr.push(fn);
},
trigger() {
for (let item of subArr) {
item.apply(this, arguments);
}
}
}
})();
// 添加两个订阅者
publish.add(function (color,size) {
console.log("订阅者1:颜色("+color+")尺码("+size+")");
});
publish.add(function (color,size) {
console.log("订阅者2:颜色("+color+")尺码("+size+")");
});
// 发布消息
publish.trigger("红色",42);
优点
- 支持简单的广播通信,一旦对象状态改变,就会自动通知订阅的对象
- 发布者和订阅者耦合性降低
缺点
创建订阅者需要消耗时间和内存,切勿大量使用。
中介者模式
中介,即连接两方的一个环节。比如:裁判
用跑男举例,比赛两方红和蓝,比赛开始前分队伍,确认队友与敌人,比赛开始后红蓝双方开始撕名牌,名牌被撕掉裁判就通知"xx被淘汰",如果某一方的所有队员的名牌都被撕掉,裁判宣判比赛结束。
function Referee(name, team) {
this.name = name; // 姓名
this.team = team; // 队伍
this.state = 'live'; // 当前状态
this.friends = []; // 队友
this.enemies = []; // 敌人
}
Referee.prototype.lose = function (allDead) {
// 如果一方全体淘汰,则该队伍失败
if (allDead) {
console.log(this.team + "团队失败!");
console.log(this.enemies[0].team + "团队胜出!");
} else {
console.log(this.team + "方" + this.name + "淘汰!");
}
};
Referee.prototype.die = function () {
this.state = 'die';
let all_dead = true;
for (let item of this.friends) {
if (item.state === 'live') {
all_dead = false;
break;
}
}
this.lose();
if (all_dead) {
this.lose(true);
}
};
let signUp = (function () {
// 队员列表
let playerList = [];
return function (name, team) {
let newPlayer = new Referee(name, team);
playerList.push(newPlayer);
// 设置每个队员的队友与敌人
for (let item of playerList) {
if (item !== newPlayer) {
if (item.team === newPlayer.team) {
newPlayer.friends.push(item);
item.friends.push(newPlayer);
} else {
newPlayer.enemies.push(item);
item.enemies.push(newPlayer);
}
}
}
return newPlayer;
}
})();
// 队员注册
let p1 = signUp('aa', "red");
let p2 = signUp('bb', "red");
let p3 = signUp('cc', "red");
let p4 = signUp('dd', "red");
let p5 = signUp('ee', "blue");
let p6 = signUp('ee', "blue");
let p7 = signUp('ee', "blue");
let p8 = signUp('ee', "blue");
// 淘汰
p1.die();
p2.die();
p3.die();
p4.die();
优点
依旧是代码更容易理解,耦合性低
结束
其实JS模式在日常代码中经常性用到,或许只是你没有细心注意。