导读:
日常开发中,一些特定的场景下你的处理方法可能并不是很理想,往往这时借助一些设计模式可以让你优雅而高效的实现这些逻辑,下面就介绍一些虽然不是最全的但一定是最常用的设计模式。
单例模式:
定义:一个类只返回一个实例,一旦创建再次调用就直接返回
使用场景:比如自定义弹窗,无论你程序中多少调用,都只应创建一个弹窗对象
class CreateUser {
constructor(name) {
this.name = name;
this.getName();
}
getName() {
return this.name;
}
};
const ProxyMode = (() => {
let instance = null;
return (name) => {
if(!instance) {
instance = new CreateUser(name);
}
return instance;
}
})();
let a = ProxyMode('vn');
let b = ProxyMode('lb');
console.log(a, b); // vn vn 单例模式只会创建一次实例
策略模式:
定义:定义一个策略类只专注与各方法算法实现,定义一个接口调用这些方法
特点:代码优雅,可读性高
// 策略类
const levelObj = {
"A": money => money * 4,
"B": money => money * 3,
"C": money => money * 2
}
// 环境类 封装调用接口
const getMoney = (level, money) => levelObj[level](money);
console.log(getMoney('A', 200)) // 800
代理模式:
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问
使用场景:比如图片懒加载,先缓存动态 loading,必要时传入 src
const imgFunc = (() => {
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: (src) => {
imgNode.src = src;
}
}
})();
const ProxyImg = (() => {
let img = new Image();
img.onload = () => {
let node = document.getElementsByTagName('img')
imgFunc.setSrc(img.src);
}
return {
setSrc: (src) => {
imgFunc.setSrc('../C3photo/jacky/1.jpg');
img.src = src;
}
}
})();
ProxyImg.setSrc('../C3photo/jacky/2.jpg');
装饰者模式:
定义:装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。
使用场景:类似于拦截器,添加对象的前置和后置事件等。
Function.prototype.before = function(beforefn) {
let _self = this; //保存原函数引用
return function(){ //返回包含了原函数和新函数的 '代理函数'
beforefn.apply(this, arguments); //执行新函数,修正this
return _self.apply(this, arguments); //执行原函数
}
}
Function.prototype.after = function(afterfn) {
let _self = this;
return function(){
let ret = _self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
}
let func = function() {
console.log('2');
}
//func1和func3为挂载函数
let func1 = function() {
console.log('1');
}
let func3 = function() {
console.log('3');
}
func = func.before(func1).after(func3);
func(); // 1 2 3
发布订阅模式:
定义:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
使用场景:微信公众号的订阅
let eventEmitter = {
list: {}, // 缓存列表(调度中心)
on(event, fn) { // 订阅
let _this = this;
_this.list[event] = _this.list[event] || [];
_this.list[event].push(fn);
return _this;
},
emit() { // 发布
let _this = this;
let event = [].shift.call(arguments), // shift 会改变原数组,因此 arguments 只剩下第二个参数
fns = _this.list[event];
if(fns && fns.length) {
fns.forEach(fn => fn.apply(_this, arguments));
}
return _this;
},
off(event, fn) { // 取消订阅
let _this = this;
let fns = _this.list[event];
if(!fns) return false; // 如果缓存列表中没有相应的 fn,返回false
if(!fn) {
// 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
fns.length = 0;
} else {
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
for (let i = 0; i < fns.length; i++) {
if (fns[i] === fn || fns[i].fn === fn) {
fns.splice(i, 1);
break;
}
}
}
}
};
const user1 = (content) => {
console.log('用户1订阅了:', content);
}
const user2 = (content) => {
console.log('用户2订阅了:', content);
}
const user3 = (content) => {
console.log('用户3订阅了:', content);
}
// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article2', user3);
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.off('article1', user1);
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
//用户1订阅了: Javascript 发布-订阅模式
//用户2订阅了: Javascript 发布-订阅模式
//用户3订阅了: Javascript 观察者模式
//用户2订阅了: Javascript 发布-订阅模式
总结:
其实当你学了这么久的前端,做了这么久的项目,回来看看设计模式会发现它的思想其实挺有意思的,当你试着在工作中写出这些设计模式,你的技术又会上一层台阶啦。