工作常用设计模式记录

101 阅读10分钟

观察者模式

三要素(绑定on 触发emit 解绑off)

观察者模式(Observer Pattern)是一种软件设计模式,它定义了一种一对多的依赖关系,使得多个对象之间的状态同步更新。在观察者模式中,一个被观察者对象(也称为主题对象)维护一个观察者列表,并在自身状态发生改变时,自动通知所有观察者进行更新。

观察者模式的核心思想是将对象间的依赖关系解耦。通常情况下,对象之间的依赖关系是直接耦合的,当一个对象的状态发生改变时,需要通知其他依赖该对象的对象进行更新,这样会使得代码变得复杂且难以维护。而观察者模式通过引入一个中介者(也称为主题对象),将对象间的依赖关系进行解耦,使得每个对象都只需要与主题对象进行交互,而不需要知道其他对象的存在。

在观察者模式中,通常会定义两个接口:一个被观察者接口和一个观察者接口。被观察者接口通常包含注册、注销和通知观察者的方法,观察者接口则包含接收主题对象通知的方法。具体实现可以根据实际情况进行扩展。

下面是一个示例代码:

class Observer {
  constructor() {
    // 全局对象,以便 存储 type 与 回调函数的关系
    this.oTypes = {
      // xxx: [f2]
    };
  }


  // 绑定
  on(type, callback) {
    // 假设 type 第一次 出现在 this.oTypes 中,即 this.oTypes[type] 返回值是 undefined
    if (!this.oTypes[type]) {
      // 表示 type 第一次 出现在 this.oTypes 中
      this.oTypes[type] = [callback];
    } else {
      // 表示 type 第 >= 2次 出现在 this.oTypes 中,this.oTypes[type] 的返回值 肯定是 数组
      this.oTypes[type].push(callback)
    }

  }


  // 触发; 注:data 是可选参
  emit(type, ...data) {
    // this.oTypes[type] 的返回值 只会是 两种类型:
    // 1. undefined 表示 type 不存在于  this.oTypes
    // 2. 数组,表示 type 存在于 this.oTypes,且 数组中都是 回调函数


    if (Array.isArray(this.oTypes[type])) {
      // 只要执行本代码块,即表示 type 存在于 this.oTypes,且 数组中都是 回调函数
      this.oTypes[type].forEach(fn => fn(...data))
    }



  }


  // 解绑
  off(type, callback) {
    // 加 判断的原因:若 this.oTypes[type] 的返回值不是数组,根本就弹出上,将 callback 从数组中 移除 
    if (Array.isArray(this.oTypes[type])) {
      // 
      this.oTypes[type] = this.oTypes[type].filter(fn => {
        return !(fn === callback);
        // 注:fn === callback 返回值 若为 true,则 表示 此时的 fn 的要被移除掉
      });
    }
  }
}


const f1 = () => {
  console.log('f1')
}


const f2 = (a, b, c) => {
  console.log('f2', a, b, c)
}


const target = new Observer();


target.on('xxx', f1);
target.on('xxx', f2);


setTimeout(() => {
  // 表示 2S 之后 触发 绑定到 `xxx` 上的 回调函数
  target.emit('xxx', '111', '222', '333', '444');
}, 2000);

策略模式

减少if/else 的使用

策略模式(Strategy pattern)是一种行为设计模式,它允许在运行时选择算法的行为。该模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。使用策略模式可以使算法的变化独立于使用算法的客户端,从而更灵活、可维护和可扩展。

在 JavaScript 中,策略模式可以通过创建一个策略对象来实现。该对象定义了一个公共接口,它封装了一系列具体的算法实现。然后,客户端代码可以选择不同的策略对象来使用不同的算法。

以下是一个示例代码:

// 设计 type 的值是后端返回的数据:
  // 前后端 约定:
    // `a` 表示 `未完成`
    // `b` 表示 `已完成`
    // `c` 表示 `进行中`
      // 注:这种约定的好处:节省网络传输资源
  // 需求:前端 根据 type 的值,进行alert 相关的 文字


const type = null;
  

/* 普通写法:
  if (type === 'a') {
    alert('未完成');
  } else if (type === 'b') {
    alert('已完成');
  } else if (type === 'c') {
    alert('进行中');
  } else { // 强调:条件分支 一定要考虑 **极端/边界 情况**:即 不给 前端 `a/b/c` 的值,而是其他
    alert('温馨提示:这不是 前端的锅...');
  }

*/

// 代码 如何 更加 `可读`; 
  // ->  尽量地 减少 条件分支;(尤其是 嵌套的条件分支);

// 用策略模式,去除 上述的 条件分支:
const dict = {
  // 声明一个函数, 名字为 `a`
  // 代码块为 alert 的代码
  a() {
    alert('未完成');
  },
  b() {
    alert('已完成');
  },
  c() {
    alert('进行中');
  },
  // 即 非 `a/b/c` 的情况,则走本 函数
  default() {
    alert('温馨提示:这不是 前端的锅...');
  }
};

// 拆解下面的代码:
  // 1. dict[type] 的返回值 是 函数 声明
  // 2. `dict[type]()` 表示 函数执行
  // 3. 若 dict[type] 的返回值为 undefined,则 表示 type 不符合 `a/b/c` 的要求,则需要走 default 函数
(dict[type] || dict.default)();

工厂模式

用来创建对象的一种模式

JavaScript中的工厂模式是一种创建对象的设计模式,通过该模式可以创建多个相似的对象。工厂模式是一种简单而又实用的设计模式,它可以将对象的创建和实现分离开来,使得代码更加灵活,易于维护和扩展。

工厂模式的核心思想是定义一个用于创建对象的工厂函数,该函数接收一些参数,根据这些参数创建一个新的对象并返回。这种方式可以隐藏对象的创建细节,将其封装在一个函数内部,使得调用者只需要知道如何使用这个函数来创建对象,而不需要了解对象的具体实现细节

下面是一个简单的工厂模式的示例:

function createPerson(name, age) {
  const person = {
    name,
    age,
    sayHello() {
      console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
  };
  return person;
}

const person1 = createPerson('Alice', 30);
const person2 = createPerson('Bob', 25);

person1.sayHello(); // Output: Hello, my name is Alice and I am 30 years old.
person2.sayHello(); // Output: Hello, my name is Bob and I am 25 years old.

在上面的示例中,createPerson函数是一个工厂函数,它接收两个参数 name 和 age,然后创建一个新的对象 person,并将这两个参数作为对象的属性,最后返回这个新创建的对象。通过调用 createPerson 函数来创建两个不同的 person 对象,然后可以通过调用对象上的 sayHello 方法来打印出相应的问候语。

工厂模式的优点是可以简化对象的创建过程,并提高代码的可读性和可维护性。同时,工厂模式也允许我们根据需要动态地创建对象,而不需要事先知道对象的具体实现细节。

单例模式

全局共享一个实例对象

在 JavaScript 中,单例模式是一种常用的设计模式,它可以保证一个类只有一个实例,并且提供全局访问点。

实现单例模式有多种方式,其中比较常见的方式是使用闭包或者静态属性来实现。

单例模式的 典型应用场景:

场景1:

比如:网页版QQ的登录框, 第一次 渲染DOM元素, 若后续还需要显示 该 登录框

不需要再次渲染;只需将 第一渲染的 DOM元素,重新 显示即可。

场景2:Dialog

注:Dialog 由两部分组成:

1)遮罩(背景灰色 - 作用:让用户专注于 弹窗)

2)弹窗容器(注:容器内 可以放置 任意的 其他的 DOM元素)

总结:Dialog 是典型的「单例模式」;即页面刷新,即 渲染了 Dialog 的相关DOM元素

在需要其显示的时候,再让其 显示。

场景3:

本页面 执行 import {add} from './add.js'; ,本质是 将 add 函数 放入 内存中。

其他页面,也执行 上述的代码;并不需要再将 add函数 放入内存中,其他页面 可以共用 同一 add的内存。

上述,也叫 「单例模式」

场景4:ES5 之前的 window

也是 典型的 「单例模式」。

使用闭包实现单例模式:

const Singleton = (function() {
  let instance;
  function createInstance() {
    const object = new Object('I am the instance');
    return object;
  }
  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true

在上面的代码中,我们使用立即执行函数创建一个闭包,将实例对象保存在闭包内部的变量 instance 中。在 getInstance 方法中,首先判断实例对象是否已经存在,如果存在则直接返回该实例对象,否则通过调用 createInstance 方法创建一个新的实例对象。

使用静态属性实现单例模式:

class Singleton {
  static instance = null;
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

在上面的代码中,我们使用静态属性 instance 来保存实例对象,如果 instance 已经存在,则返回该实例对象,否则通过 new 关键字创建一个新的实例对象并保存在 instance 中。

无论使用闭包还是静态属性,都可以实现单例模式,它们的核心思想都是利用闭包或静态属性保存实例对象,从而保证一个类只有一个实例,并且提供全局访问点。

总结:单例模式的作用:但凡是 只需要 一次声明/创造/渲染... ,那么 就不需要重复地 声明/创造...

原型模式

在 JavaScript 中,每个对象都有一个原型(prototype)属性,它指向另一个对象,这个对象就是当前对象的父对象。原型模式就是利用这个特性,实现对象之间的继承。

具体来说,当我们创建一个对象时,如果没有指定它的原型,则它的原型会默认指向 Object.prototype,即它继承了 Object 对象的所有属性和方法。如果我们希望创建一个自定义的对象,并让它继承一些其他对象的属性和方法,就可以通过设置它的原型来实现。

例如,我们可以定义一个 Person 类,并设置它的原型为一个 Animal 对象,这样 Person 对象就可以继承 Animal 对象的属性和方法。代码如下:

function Animal() {
  this.type = 'animal';
}

Animal.prototype.say = function() {
  console.log('I am a ' + this.type);
}

function Person(name) {
  this.name = name;
}

Person.prototype = new Animal();

var person = new Person('Bob');
person.say(); // 输出:I am a animal

在上面的代码中,我们定义了一个 Animal 类,它有一个 type 属性和一个 say 方法。然后定义了一个 Person 类,它有一个 name 属性。最后将 Person 类的原型设置为一个 Animal 对象,这样 Person 对象就可以继承 Animal 对象的所有属性和方法,包括 type 属性和 say 方法。最后创建一个 Person 对象,并调用它的 say 方法,输出结果为“I am a animal”。

通过原型模式,我们可以轻松实现对象之间的继承,并实现代码的复用。

迭代器模式

在 JavaScript 中,迭代器模式是一种用于遍历集合中元素的设计模式。它可以使我们以一种简单、统一的方式访问集合中的元素,而不必关心集合的内部实现方式。

具体来说,迭代器模式包括两个核心部分:迭代器对象和可迭代对象。迭代器对象负责遍历可迭代对象中的元素,而可迭代对象则包含了一些可供遍历的元素。

在 JavaScript 中,迭代器对象通常是一个包含 next() 方法的对象,每次调用 next() 方法可以返回集合中的下一个元素。而可迭代对象通常是一个实现了 Symbol.iterator 方法的对象,它返回一个迭代器对象。

下面是一个简单的迭代器模式的例子:

const numbers = [1, 2, 3, 4, 5];

const iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: 5, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

在上面的代码中,我们创建了一个包含 5 个元素的数组 numbers,然后获取了它的迭代器对象 iterator,通过调用 next() 方法,我们可以遍历整个数组中的元素。

迭代器模式可以帮助我们简化代码,使其更加模块化和可复用。在实际开发中,它通常用于处理大量数据的情况下,以及需要对数据进行遍历和过滤的情况下。

代理模式

通过代理对象间接操作对象,实现数据拦截

const model = {
	name: 'zhangsan',
	age: 18
}
function render(data) {
	return document.querySelector('#app').innerHTML = `
        <div>${data.name}</div>
        <div>${data.age}</div>
        <div>${data.sex}</div>
        `
}
function observer(model,render){
	return new Proxy(model,{
		get(target, key, receiver) {
			if (Reflect.has(target, key)) {
				return Reflect.get(target, key, receiver);
			}
			Reflect.set(target,key,'default',receiver);
			renderFn(target);
			return '默认值';
		},
		set(target, key, value, receiver) {
			console.log(`setter...${value}`);
			Reflect.set(target, key, value, receiver)
			renderFn(target);
		},
		has(target, key) {
			// if(!key in target){
			//    return '默认值'; 
			// }
			if (!Reflect.has(target, key)) {
				Reflect.set(target, key, '默认值');
				renderFn(target);
				return Reflect.set(target, key, '默认');

			}
		}
	})
}