浅聊一下js中的那些设计模式

204 阅读5分钟

前言

再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组件内的ToastNotify呢都是用到了单例模式,下面我们看一下他们的单例模式是如何进行实现的

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]
}

结尾

以上呢就是比较常见的一些设计模式了,有些比较常用,有些可能不常用到,所谓设计模式呢其实就是再开发的时候一些好的设计进行了提取,这些设计模式呢可能日常开发的话用到的不多,但是学习完成这个对于后期一些源码的理解和学习上会通顺一些