前言
再js中存在与很多设计模式,例如常见的单例模式,观察者模式,发布订阅模式等,虽然再日常的开发中可能很少会用到这样的设计模式,但是对于有些地方确实用起来比较简洁舒适,例如,我现在定义了一组数据跨模块共享,可以跨模做数据处理,调用实例方法添加或者删除数据。这样除了使用到vuex pinia 还可以使用到我们的订阅发布模式。下面逐一讲解那些常见的设计模式
单例模式
单例模式的表现形式为,整个系统需要保证只有一个实例存在
class SingleTon {
static #instance;
static getInstance() {
if (!this.#instance == undefined) {
this.#instance = new SingleTon();
}
return this.#instance;
}
}
const a = SingleTon.getInstance();
const b = SingleTon.getInstance();
console.log(a === b); // true
下面我们可以看一下vant组件库内使用到的单例模式,在vant组件内的Toast 和 Notify呢都是用到了单例模式,下面我们看一下他们的单例模式是如何进行实现的
function getInstance() {
if (!queue.length || c) {
const instance = createInstance();
queue.push(instance);
}
return queue[queue.length - 1];
}
/**
* Display a text toast
*/
export function showToast(options: string | ToastOptions = {}) {
if (!inBrowser) {
return {} as ToastWrapperInstance;
}
const toast = getInstance();
const parsedOptions = parseOptions(options);
toast.open(
extend(
{},
currentOptions,
defaultOptionsMap.get(parsedOptions.type || currentOptions.type!),
parsedOptions,
),
);
return toast;
}
这个是Toast组件的showToast方法, toast调用方法获取实例,那么此时getInstance内先判断queue的长度,此时queue的长度为0, 那么就睡进入if内首次创建实例push到queue内,那么下次就会变为false,allowMultiple默认为false,因为vant组件也支持创建多个。Notify呢也差不多就不讲了,感兴趣的可以再gitup上看一下。
工厂模式
工厂模式的表现形式为,只要调用即可返回一个全新的对象
//工厂模式
function a(name, age) {
return { name, age };
}
const person1 = a('John', 30);
//构造函数
function b(name, age) {
this.name = name;
this.age = age;
}
const person2 = new b('Jane', 25);
这里为什么还要写一个构造函数呢,是因为这两个看起来效果是一样的,都是返回一个对象那么工厂模式的优势是什么呢。对于构造构造函数这种方式来说,同一个构造函数的每个根实例共享相同的全局配置,这样容易造成实例混乱。工厂模式的表现在于每个对象都有自己的实例,不会收到影响,例如axios,当我需要两个axios的时候使用axios.create是会给我返回了两个实例,那么他们两个之间就不会相互造成影响
import axios from "axios";
// 创建一个 axios 实例
const request = axios.create({
baseURL: "/dev", // 设置基础 URL
timeout: 10000, // 设置请求超时时间
});
观察者模式
观察者模式是我们都比较熟悉的,他呢是在对象之间定义一个一对多的依赖,当被观察者的状态被改变的时候所有的依赖都会自动接收到通知。
window.addEventListener('load',()=>{console.log('load触发了');})
// vue
watch: {
obj(newvalue){
console.log(newvalue);
}
}
上方呢就是我们经常用到的观察者模式。观察者模式下目标对象只能存在一个,观察者可以是多个,当目标对象状态发生改变的时候观察者触发对应的函数
发布订阅模式
发布订阅模式呢和上方的观察者模式相似,只不过是多了个eventBus事件总线,发布订阅模式的流程是这样的,首先呢通过$on进行事件的注册,$emit进行事件的触发,$off进行事件的移除,$once事件触发一次后移除
class eventBus {
#list = {}
$on(eventName, callback) {
if (this.#list[eventName] === undefined) {
this.#list[eventName] = []
}
this.#list[eventName].push(callback)
}
$emit(eventName, ...age) {
const funcs = this.#list[eventName] || []
funcs.forEach((callback) => {
callback(...age)
})
}
$off(eventName) {
this.#list[eventName] = []
}
$once(eventName, callback) {
this.$on(eventName, (...age) => {
callback(...age)
this.$off(eventName)
})
}
}
发布订阅模式呢也是一对多的,和上方的观察者模式相似,但是呢是通过实现总线来进行调度的
原型模式
原型模式呢是一种创建型模式,主要是通过辅助一个已经存在的实例来返回一个新的实例,而不知新建一个实例,并且返回的实例呢是不会影响到之前的实例的
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
ob.dep.notify()
}
return result
})
})
上方呢是vue2的源码片段,它主要呢是通过Object.create(arrayProto)拿到了数组原型上的方法。原型模型的核心呢就是Object.create()他是通过拿到当前对象返回一个行的实例对象。然后vue对这些方法进行了改写来完成响应式
缓存代理
缓存代理呢是为对象提供一个占位符或者一个代用品,来控制对他的访问,例如通过neme查询一个list。当我们用这个name查询过数据的时候下次就可以从代理缓存内直接拿到数据,而不必去访问服务器
const obj = {}
function search(name){
if(!obj[name]){
obj[name] = await axios({
url: '/api/search',
params: {
name,
}
})
}
return obj[name]
}
结尾
以上呢就是比较常见的一些设计模式了,有些比较常用,有些可能不常用到,所谓设计模式呢其实就是再开发的时候一些好的设计进行了提取,这些设计模式呢可能日常开发的话用到的不多,但是学习完成这个对于后期一些源码的理解和学习上会通顺一些