前端开发中常见的7种设计模式

2,082 阅读4分钟

历史背景

1995年,4位面向对象领域的前辈著作《设计模式:可复用面向对象软件基础》中提出了23种设计模式,总结出的固定的编码套路,在编程界达成共识,成为了标准的沟通语言,后续从业人员在软件编写过程中直接套用即可。

当时还没有前端的概念,并非所有模式都适合前端开发。

前端常见的7种设计模式

前端常用的模式:工厂模式、单例模式、观察者模式、迭代器模式、原型模式、装饰器模式、代理模式。

1、工厂模式

创建对象的一种方式,不用每次都亲自创建对象,而是通过一个既定的“工厂”来生产对象。

普通方法:

function Food(a, b) {
    // 、、、、
}
let food
if (a) {
    food = new Food(x);
}
if (b) {
    food = new Food(x, y);
}

工厂方法:

function Food(a, b) {
    // 、、、、
}
function createFood(a, b) {
    if (a) {
        food = new Food(x);
    }
    if (b) {
        food = new Food(x, y);
    }
}
const food1 = createFood(a);
const food2 = createFood(a, b);

优点

  1. 把逻辑判断封装到工厂函数中,只需传入不同的参数,获得不同的结果;
  2. 符合开放封闭原则;
  3. 代码复用性好;

场景

  1. jQuery:将逻辑用工厂函数封装起来,挂载到window.上,使用时上,使用时('.div').addClass('abc')
class JQuery {
  selector: string
  length: number
  constructor(selector: string) {
    const domList = Array.from(document.querySelectorAll(selector));
    const length = domList.length;
    for (let index = 0; index < length; index++) {
      this[index] = domList[index];
    }
    this.selector = selector;
    this.length = length;
  }
  append(ele: HTMLElement) {
    // 、、、
    return this;
  }
}
function $(selector: string) {
  return new JQuery(selector);
}
window.$ = $;
  1. vue中render函数中生成vnode的方法:_createElementVNode()

例子

  1. React.createElement

例子

2、单例模式

一个系统里只能有一个实例,且只能被创建一次,并缓存起来,一直使用。

全局唯一的思想。

function getSingleTon() {
  let instance;
  class SingleTon { }
  return () => {
    if (!instance) {
instance = new  SingleTon ();
}
    return instance;
  }
}
const instance = getSingleTon();
const abc = instance();
const acd = instance();
console.log(abc === acd); // true

优点

  1. 节约性能,精简代码
  2. 数据都在一个实例里,便于数据通信,数据流清晰

场景

  1. Vuex中store

  2. eventBus

3、观察者模式

一开始监听后就不管了,等到观察目标有了行为后,通知到观察者,然后做出对应的反应。无需实时监听,耗费性能。

UI交互层面几乎都是观察者模式。

class Subject {
  private state: number = 0
  private observers: Observer[] = []
  getState() {
    return this.state
  }
  setState(newState: number) {
    this.state = newState
    this . notify ()
  }
  addObserver(observer: Observer) {
    this.observers.push(observer)
  }
  private notify() {
    this.observers.forEach(observer => {
      observer.update(this.state)
    })
  }
}
class Observer {
  name: string = ''
  constructor(name: string) {
    this.name = name
  }
  update(state: number) {
    console.log(this.name)
    console.log(state)
  }
}
const sub = new Subject()
const observer = new Observer('abc')
sub.addObserver(observer)

sub.setState(8)

场景

  1. dom事件:绑定好了只待触发
  2. vue生命周期:钩子函数
  3. vue的watch
  4. 异步回调:setTimeout、Promise.then
  5. mutationObserver

观察者模式 vs 发布订阅模式

观察者模式:观察者和发布者直接关联,如addEventListener

发布订阅模式:观察者和发布者互不认识,中间有媒介,如event自定义事件,需要eventbus或mitt作为媒介

注意事项

自定义事件注意解绑,防止内存泄漏

4、迭代器模式

对有序结构和有序数据更优雅、更符合设计原则的遍历。

参考:es6.ruanyifeng.com/#docs/itera…

普通for循环的缺点:

for (let index = 0; index < array. length; index++) { // 暴露长度
  const element = array[index]; // 必须知道索引才能获取对应的值
}

不完全的迭代器:

const dom = document.querySelectorAll('div')
dom.forEach((item) => console.log(item)) // 无需太多内部信息即可获取item

简单的迭代器实现:

class MyInterator {
  data = []
  index = 0
  constructor(data) {
    this.data = data;
  }
  next() {
    if (this.hasNext()) {
      return this.data[this.index++];
    }
    return null;
  }
  hasNext() {
    if (this.index >= this.data.length) return false;
    return true;
  }
}
const content = new MyInterator([1, 2, 3, 4, 5, 6]);
while (content.hasNext()) {
  const num = content.next();
  console.log(num);
}

js有序对象内置迭代器Symbol.interator方法:字符串、数组、NodeList、Map、Set、arguements。

const arr = [1, 4, 5, 2]
const interator = arr[Symbol.iterator]()
interator.next()

作用

1、用于for...of遍历内置的interator

2、数组解构、扩展操作符、Array.form

3、用于Promise.all()、Promise.race()

5、原型模式(不常用)

复制一个已创建的实例,来创建一个和原实例相似的新对象。

场景

Object.create指定原型

const obj2 = Object.create({x: 100}) obj2.proto // {x:100}

6、装饰器模式

向一个现有的对象添加新的功能,同时又不改变其结构和原有功能,属于结构型模式,它是作为现有的类的一个包装,动态地给一个对象添加一些额外的职责。

一个功能外挂,功能扩展。

class Circle {
    draw() {
        console.log('画一个圆')
    }
}
class Decorator {
    private circle: Circle
    constructor(circle: Circle) {
        this.circle = circle
    }
    draw() {
        this.circle.draw() // 不改变原有方法
        this.setBorder() // 装饰的内容,扩展方法,对扩展开放
    }
    private setBorder() {
        console.log('设置边框颜色')
    }
}
const circle = new Circle()
circle.draw()
const decorator = new Decorator(circle)
decorator.draw()

es6写法

function testName(target) { // 装饰器
    // target Foo对象
    target.abc = true;
}
@testName // 将对目标作为对象传入装饰器
class Foo {
    static abc
}

7、代理模式

为其他对象提供一种代理以控制对这个对象的访问,用户不得直接访问对象。

提供一个访问对象的中间层、代理方,会更加方便或避免直接访问引起的一些副作用。

class RealImg {
    fileName
    constructor(fileName) {
        this.fileName = fileName
        this.loadFromDist()
    }
    display() {
        this.loadFromDist();
        console.log('display...', this.fileName)
    }
    private loadFromDist() {
        console.log('loading...', this.fileName)
    }
}
class ProxyImg {
    readImg: RealImg
    constructor(fileName) {
        this.readImg = new RealImg(fileName)
    }
    display() {
        // 做一些限制或判断等处理
        this.readImg.display()
    }
}
const proxImg = new ProxyImg('xxx.png') // 使用代理
proxImg.display()

场景

1、DOM事件代理(委托)

2、webpack devServer代理

3、Nginx反向代理

4、vue3 proxy 追踪属性访问