设计模式是前人总结出来的一些典型的代码设计方案。了解一下,对提升架构能力会有所帮助。
根据设计模式的参考书《Design Patterns - Elements of Reusable Object-Oriented Software》中所提到的,总共有23种设计模式。 这些模式可以分为三大类:
创建型模式(Creational Patterns):这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
结构型模式(Structural Patterns):这些设计模式关注类和对象的组合。
行为型模式(Behavioral Patterns):这些设计模式特别关注对象之间的通信。
另外还有一些由Sun Java Center提出的J2EE 设计模式,这些模式特别关注表示层:
提示
观察者模式(Observer) 通常又被称为发布-订阅者模式或消息机制, 它定义了对象间的一种一对多的依赖关系, 只要当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新, 解决了主体对象与观察者之间功能的耦合, 即一个对象状态改变给其他对象通知的问题。
下面这段代码就是一种发布-订阅模式:
document.querySelector('#btn').addEventListener('click', function () {
alert('You click this button');
}, false)
document.querySelector('#btn').addEventListener('click', function () {
alert('You click this button');
}, false)
除了我们常见的 DOM 事件绑定外,观察者模式应用的范围还有很多~
比如 vue2 框架里不少地方都涉及到了观察者模式,比如:
数据的双向绑定
利用Object.defineProperty()对数据进行劫持, 设置一个监听器Observer,用来监听所有属性, 如果属性上发上变化了,就需要告诉订阅者Watcher去更新数据, 最后指令解析器Compile解析对应的指令, 进而会执行对应的更新函数,从而更新视图,实现了双向绑定。
子组件与父组件通信
Vue中我们通过props完成父组件向子组件传递数据, 子组件与父组件通信我们通过自定义事件即$on、$emit来实现, 其实也就是通过$emit来发布消息,并对订阅者$on做统一处理。
首先我们需要创建一个观察者对象,它包含一个消息容器和三个方法,分别是:
const Observe = (function() {
// 防止消息队列暴露而被篡改,将消息容器设置为私有变量
let __message = {};
return {
// 注册消息接口
on: function(type, fn) {
// 如果此消息不存在,创建一个该消息类型
if (typeof __message[type] === 'undefined') {
// 将执行方法推入该消息对应的执行队列中
__message[type] = [fn];
} else {
// 如果此消息存在,直接将执行方法推入该消息对应的执行队列中
__message[type].push(fn);
}
},
// 发布消息接口
publish: function(type, args) {
// 如果该消息没有注册,直接返回
if (!__message[type]) return;
// 定义消息信息
const events = {
type, // 消息类型
args: args || {} // 参数
}
// 遍历执行函数
for (let i = 0, len = __message[type].length; i < len; i++) {
// 依次执行注册消息对应的方法
__message[type][i].call(this, events)
}
},
// 移除消息接口
off: function(type, fn) {
// 如果消息执行队列存在
if (__message[type] instanceof Array) {
// 从最后一条依次遍历(之所以从最后一个开始遍历,是因为下面会用到splice)
for (let i = __message[type].length - 1; i >= 0; i--) {
// 如果存在该执行函数则移除相应的动作
__message[type][i] === fn && __message[type].splice(i, 1);
}
}
}
}
})();
const Observe = (function() {
// 防止消息队列暴露而被篡改,将消息容器设置为私有变量
let __message = {};
return {
// 注册消息接口
on: function(type, fn) {
// 如果此消息不存在,创建一个该消息类型
if (typeof __message[type] === 'undefined') {
// 将执行方法推入该消息对应的执行队列中
__message[type] = [fn];
} else {
// 如果此消息存在,直接将执行方法推入该消息对应的执行队列中
__message[type].push(fn);
}
},
// 发布消息接口
publish: function(type, args) {
// 如果该消息没有注册,直接返回
if (!__message[type]) return;
// 定义消息信息
const events = {
type, // 消息类型
args: args || {} // 参数
}
// 遍历执行函数
for (let i = 0, len = __message[type].length; i < len; i++) {
// 依次执行注册消息对应的方法
__message[type][i].call(this, events)
}
},
// 移除消息接口
off: function(type, fn) {
// 如果消息执行队列存在
if (__message[type] instanceof Array) {
// 从最后一条依次遍历(之所以从最后一个开始遍历,是因为下面会用到splice)
for (let i = __message[type].length - 1; i >= 0; i--) {
// 如果存在该执行函数则移除相应的动作
__message[type][i] === fn && __message[type].splice(i, 1);
}
}
}
}
})();
使用:
// 订阅消息
Observe.on('say', function (data) {
console.log(data.args.text);
})
Observe.on('success',function () {
console.log('success')
});
//发布消息
Observe.publish('say', { text : 'hello world' } )
Observe.publish('success');
// 订阅消息
Observe.on('say', function (data) {
console.log(data.args.text);
})
Observe.on('success',function () {
console.log('success')
});
//发布消息
Observe.publish('say', { text : 'hello world' } )
Observe.publish('success');
我们在消息类型为say、success的消息中注册了两个方法,其中有一个接受参数,另一个不需要参数, 然后通过publish发布say和success消息,结果跟我们预期的一样, 控制台输出了 hello world 以及 success。
class EventBus {
constructor() {
this.event = Object.create(null);
};
// 注册事件
on(name, fn) {
if(!this.event[name]){
// 一个事件可能有多个监听者
this.event[name]=[];
};
this.event[name].push(fn);
};
// 触发事件
emit(name, ...args) {
//给回调函数传参
this.event[name] && this.event[name].forEach(fn => {
fn(...args)
});
};
// 只被触发一次的事件
once(name, fn) {
// 在这里同时完成了对该事件的注册、对该事件的触发,并在最后取消该事件。
const cb=(...args)=>{
//触发
fn(...args);
//取消
this.off(name,fn);
};
//监听
this.on(name, cb);
};
// 取消事件
off(name, offCb) {
if(this.event[name]){
const index = this.event[name].findIndex((fn)=>{
return offCb === fn;
})
if (index !== -1) {
this.event[name].splice(index, 1);
if(!this.event[name].length){
delete this.event[name];
}
}
}
}
}
class EventBus {
constructor() {
this.event = Object.create(null);
};
// 注册事件
on(name, fn) {
if(!this.event[name]){
// 一个事件可能有多个监听者
this.event[name]=[];
};
this.event[name].push(fn);
};
// 触发事件
emit(name, ...args) {
//给回调函数传参
this.event[name] && this.event[name].forEach(fn => {
fn(...args)
});
};
// 只被触发一次的事件
once(name, fn) {
// 在这里同时完成了对该事件的注册、对该事件的触发,并在最后取消该事件。
const cb=(...args)=>{
//触发
fn(...args);
//取消
this.off(name,fn);
};
//监听
this.on(name, cb);
};
// 取消事件
off(name, offCb) {
if(this.event[name]){
const index = this.event[name].findIndex((fn)=>{
return offCb === fn;
})
if (index !== -1) {
this.event[name].splice(index, 1);
if(!this.event[name].length){
delete this.event[name];
}
}
}
}
}
// 单例构造函数
function CreateSingleton (name) {
this.name = name;
this.getName();
};
// 获取实例的名字
CreateSingleton.prototype.getName = function() {
console.log(this.name)
};
// 单例对象
const Singleton = (function(){
var instance;
return function (name) {
if(!instance) {
instance = new CreateSingleton(name);
}
return instance;
}
})();
// 创建实例对象1
const a = new Singleton('a');
// 创建实例对象2
const b = new Singleton('b');
// 返回true
console.log(a===b);
// 返回true
console.log(a.name === b.name)
// 单例构造函数
function CreateSingleton (name) {
this.name = name;
this.getName();
};
// 获取实例的名字
CreateSingleton.prototype.getName = function() {
console.log(this.name)
};
// 单例对象
const Singleton = (function(){
var instance;
return function (name) {
if(!instance) {
instance = new CreateSingleton(name);
}
return instance;
}
})();
// 创建实例对象1
const a = new Singleton('a');
// 创建实例对象2
const b = new Singleton('b');
// 返回true
console.log(a===b);
// 返回true
console.log(a.name === b.name)
责任链模式、观察者模式、策略模式 这三种在日常的前端开发中,经常遇到:
责任链模式通常在分布提交表单中,前一步表单满足后才能进入下一步,例如新建商品、营促销活动等;
观察者模式通常应用在组件之间的通讯中;
策略模式通常用来优化过多的 if/else
或 switch/case
;
那么单例模式有哪些场景使用呢?
不借助第三方库,我们可以使用单例模式来制作一个全局的状态存储。
例如在小程序这种移动端,需要开发一个新建商品的需求,由于商品的属性很多,会将基本信息、规格属性、商品详情(富文本)等做成三个页面,规格属性选择又会多出一个页面。
总共 4 个页面以及各种组件,都需要能共享到“商品”这个对象用来进行回显。
这个时候就可以用单例模式来存储“商品”数据:
// store.js
const PRODUCT_MODEL = Object.freeze({
productName: "",
productBrand: "",
productSkuList: [],
// etc...
});
class Storage {
static getInstance() {
if (!this.instance) {
this.instance = new Storage();
}
return this.instance;
}
constructor() {
this.data = Object.assign({}, PRODUCT_MODEL);
}
init(obj) {
this.data = { ...this.data, ...obj };
}
set(key, value) {
if (!key) {
throw new Error("A store key must be provided");
}
this.data[String(key)] = value;
return this;
}
get(key) {
const value = key ? this.data[String(key)] : this.data;
return value;
}
removeItem(key) {
delete this.data[String(key)];
}
reset(obj = {}) {
this.data = Object.assign({}, PRODUCT_MODEL, obj);
}
}
module.exports = Storage.getInstance();
// store.js
const PRODUCT_MODEL = Object.freeze({
productName: "",
productBrand: "",
productSkuList: [],
// etc...
});
class Storage {
static getInstance() {
if (!this.instance) {
this.instance = new Storage();
}
return this.instance;
}
constructor() {
this.data = Object.assign({}, PRODUCT_MODEL);
}
init(obj) {
this.data = { ...this.data, ...obj };
}
set(key, value) {
if (!key) {
throw new Error("A store key must be provided");
}
this.data[String(key)] = value;
return this;
}
get(key) {
const value = key ? this.data[String(key)] : this.data;
return value;
}
removeItem(key) {
delete this.data[String(key)];
}
reset(obj = {}) {
this.data = Object.assign({}, PRODUCT_MODEL, obj);
}
}
module.exports = Storage.getInstance();
这份 store.js
模块,暴露了几个函数用来共享给页面和组件:
init()
用来在编辑模式下回填接口返回的商详数据;
reset()
用来清空并重置当前存储的数据;
// 在保存或某个场景结束操作时,需要重置单例所存储的数据
const store = require("./store.js");
onUnload() {
storage.reset();
}
// 在保存或某个场景结束操作时,需要重置单例所存储的数据
const store = require("./store.js");
onUnload() {
storage.reset();
}
set()
用来设置某个属性的值,同时它返回了 this
,这样可以链式调用:
const store = require("./store.js");
store.set("productName", "商品名称").set("productBrand", "商品品牌");
const store = require("./store.js");
store.set("productName", "商品名称").set("productBrand", "商品品牌");
get()
用来获取指定属性或全部属性的值;
const store = require("./store.js");
onShow() {
// 获取全部属性的值
const productInfo = storage.get();
// 获取指定属性的值
const productName = storage.get("productName");
}
const store = require("./store.js");
onShow() {
// 获取全部属性的值
const productInfo = storage.get();
// 获取指定属性的值
const productName = storage.get("productName");
}
removeItem()
用来移除某个属性的值;