常见的设计模式
1.工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装饰模式: (@装饰器的用法)
6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
单例模式
一个类只有一个实例,并提供一个访问他的全局访问点 一个标准的单例模式是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象 代码 demo
/**
* 单例模式
* @param {*} name
*/
function Singleton(name) {
this.name = name;
}
/**
* 获取实例
*/
Singleton.getInstance = (() => {
let instance = null;
return (name) => {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
const a = Singleton.getInstance("张三");
const b = Singleton.getInstance("里斯");
console.log(a === b); // true
使用场景
变动频繁的提示框
VueX 的全局 store 就是一个典型应用
优点
节约资源,保证访问的一致性。
缺点
扩展性不友好,因为单例模式一般自行实例化,没有接口。
工厂模式
根据不同的参数,返回不同类的实例 代码 demo
/**
* 工厂模式
*/
class Factory {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const factory = new Factory("张三");
const factory2 = new Factory("里斯");
console.log(factory.getName(), factory2.getName());
使用场景
vue3 中的 h 函数创建虚拟 DOM 节点 (vnode)。 vue-Router 源码中的工厂模式
export default class VueRouter {
constructotr(options) {
const mode = options.mode || "hash";
switch (mode) {
case "history":
this.history = new HTML5History(this, options.base);
break;
case "hash":
this.history = new HashHistory(this, options.base, this.fallback);
break;
case "abstract":
this.history = new AbstractHistory(this, options.base);
break;
default:
if (process.env.NODE_ENV !== "production") {
assert(false, `invalid mode: ${mode}`);
}
}
}
}
优点
良好的封装,访问者无需了解创建过程,代码结构清晰 扩展性良好,通过工厂方法隔离了用户和创建流程,符合开闭原则 解耦了高层逻辑和底层产品类,符合最少知识原则,不需要的就不要去交流
缺点
给系统增加了抽象性,带来了额外的系统复杂度
策略模式
定义好需要的策略,然后根据传入的策略名去执行对应的策略
const memberMap = {
// 青铜九折
bronze: (price) => {
return price * 0.9;
},
// 白银七折
silver: (price) => {
return price * 0.7;
},
// 黄金五折
gold: (price) => {
return price * 0.5;
},
};
优点
策略相互独立,可以互相切换。提高了灵活性以及复用性 扩展性好
缺点
策略相互独立,一些复杂的算法逻辑无法共享,造成资源浪费
观察者模式
观察者模式:一个对象(称为 subject 被观察者)维持一系列依赖于它的对象(称为 observer 观察者),将有关状态的任何变更自动通知给它们(观察者) 代码 demo
const observerKey = 0;
/**
* 被观察者
*/
class Subject {
constructor() {
this.observers = [];
}
/**
* 添加观察者
* @param {*} observer
*/
addObserver(observer) {
this.observers.push(observer);
}
/**
* 删除观察者
* @param {*} observer
*/
removeObserver(observer) {
this.observers = this.observers.filter((obs) => obs.key !== observer.key);
}
/**
* 通知所有观察者
*/
notify() {
this.observers.forEach((obs) => {
obs.update(this);
});
}
}
/**
* 观察者
*/
class Observer {
constructor() {
this.key = observerKey++;
}
/**
* 更新观察者
* @param {*} subject
*/
update(subject) {}
}
使用场景
防疫通知群众接种疫苗
优点
目标变化就会通知观察者,这是观察者模式最大的优点。
缺点
不灵活。目标和观察者是耦合在一起的,要实现观察者模式,必须同时引入被观察者和观察者才能达到响应式的效果
发布-订阅模式
发布-订阅模式其实是 1 对多的关系,订阅者先把事件订阅到调度中心,然后发布者需要执行哪个事件,就把该事件的名称发到调度中心,调度中心则调用该事件对应的函数。 vue2.0 的内部很多实现就是利用这个模式
class PublishSubscription {
constructor() {
// 调度中心, 记录事件,订阅者
this.subObj = {};
}
/**
* 订阅者
* @param {*} type 事件类型
* @param {*} fn 事件回调函数
*/
on(type, fn) {
if (!this.subObj[type]) {
this.subObj[type] = [fn];
} else {
this.subObj[type].push(fn);
}
}
/**
* 取消订阅
* @param {*} type 事件类型
* @param {*} fn 事件回调函数
*/
off(type, fn) {
if (this.subObj[type]) {
if (fn) {
const fns = this.subObj[type];
this.subObj[type] = fns.filter((item) => item !== fn);
}
} else {
delete this.subObj[type];
}
}
/**
* 发布事件
* @param {*} type 事件类型
* @param {*} data 数据
*/
emit(type, data) {
if (!this.subObj[type]) return;
this.subObj[type].forEach((fn) => {
fn(data);
});
}
}
// 测试
const publishSubscription = new PublishSubscription();
const fn1 = () => {
console.log("f1");
};
const fn2 = () => {
console.log("f2");
};
const type = "aa";
publishSubscription.on(type, fn1);
publishSubscription.on(type, fn2);
publishSubscription.emit(type);
publishSubscription.off(type, fn1);
publishSubscription.emit(type);
使用场景
我们微信会关注很多公众号,公众号有新文章发布时,就会有消息及时通知我们文章更新了
Vue 双向绑定中的发布订阅模式
优点
时间解耦:注册的订阅行为由发布者决定何时调用,订阅者无需持续关注,由发布者负责通知。 对象解耦:发布者无需知道消息的接受者,只需遍历订阅该消息类型的订阅者发送消息,解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象。
缺点
资源消耗:创建订阅者需要一定的时间和内存。 增加复杂度:弱化了联系,难以维护调用关系,增加了理解成本。