前端设计模式 | 青训营

59 阅读4分钟

什么是设计模式

设计模式是解决方案模型

一共有23种设计模式,分为三大类型

  • 创建型,如何创建一个对象
  • 结构型,如何灵活的将对象组装成较大的结构
  • 行为型,负责对象间的高效通信和职责划分

浏览器中的设计模式一般有以下两种

  • 单例模式
  • 发布订阅模式

其中,单例模式属于创建型,发布订阅模式属于行为型

单例模式

  1. 定义:全局唯一访问对象
  2. 应用场景:缓存,全局状态管理等

用单例模式实现请求缓存

单例模式是指类只有一个实例,一般会将这个实例放在类里, 只能通过类里的方法访问

  1. 创建: 在类的内部创建,如果创建过就返回之前创建的实例
  2. 访问: 提供一个公共的静态方法来访问实例(优点:可以限制单例对象的访问权限)

用传统的面向对象的思维实现单例模式

创建一个类,类里有一个static类型的变量用于存储该类的唯一实例,在类的外部不能通过new关键字创建对象,要获取该对象的唯一实例只能通过其中的静态方法getInstance, 该方法会判断唯一实例是否存在,存在则返回已经存在的实例,不存在则会new一个实例并赋值给类中instance变量

// 用class 实现单例模式
import { api } from "./utils"
export class Requset {
    static instance: Request    // instance用于保存单例
    private cache: Record<string, string>

    // 构造函数,在创建实例的时候初始化一个cache用于存储缓存的内容
    constructor() {
        this.cache = {}
    }
    // 获取单例
    static getInstance() {
        if(this.instance)    return this.instance
        this.instance = new Request()
        return this.instance
    }
    // 缓存url及其响应结果
    public async request(url: string){
        // 判断是否缓存
        if(this.cache[url]){
            return this.cache[url]
        }
        const response = await api(url)
        this.cache[url] = response

        return response
    }
}
// 使用Request缓存响应以及读取缓存结果
test("should response more than 500ms with class", async () => {
    // 获取单例,如果单例存在返回,不存在在类内部new一个
    const request = Request.getInstance()

    const startTime = Date.now()
    await request.request("/user/1")
    const endTime = Date.now()

    const costTime = end - startTime
    expect(costTime).toBeGreaterThanOrEqual(500)
})

test("should response quickly second time with class", async () => {
    // 获取单例,如果单例存在返回,不存在在类内部new一个
    const request1 = Request.getInstance()  
    await request1.request("/user/1")

    const startTime = Date.now()
    const request2 = Requset.getInstance()    // request1和request2 是同一个实例
    await request2.request("/user/1")
    const endTime = Date.now()

    const costTime = end - startTime
    expect(costTime).toBeLessThan(50)
})

用Javascript特有的语法模式实现单例模式

在传统的面向对象语言中,不能直接export一个方法,所以一定要创建一个类,并在类中new一个对象,通过export一个类,并通过类中的方法得到单例对象

而在javascript 中,一个module可以直接运用export去向外提供模块中的一个方法,因此单例模式可以简化成如下的形式

import { api } from "./utils"

const cache: Record<string, string> = {}
export const request = async (url: string) = {
     if(this.cache[url]){
            return this.cache[url]
        }
        const response = await api(url)
        this.cache[url] = response

        return response
}

发布订阅模式

  1. 定义:一种订阅机制,可以在被订阅对象发生变化时通知订阅者
  2. 应用场景:从系统架构之间的解耦,到业务中一些实现模式,如邮件订阅,上线订阅等

用发布订阅模式实现用户上线订阅

发布订阅模式涉及订阅者和被订阅者,在前端中因为函数可以作为参数传递,所以订阅者也可以是一个函数

在以下的例子中,User代表发布者对象,通过 subscribe 方法来订阅特定的事件名称和相应的回调函数。当状态改变时,通过 online 方法来发布对应的事件,并传递相关的数据给订阅者的回调函数。

type Notify = (user:User) => void    // 订阅者(订阅者也可以是一个函数)
export class User {
    name:string
    status: "offline" | "online"
    followers: {user: User; notify: Notify}[]    // 存储不同的user对应的回调函数(订阅)

    constructor(name: string){
        this.name = name
        this.status = "offline"
        this.followers = []
    }
    // 订阅
    subscribe(user: User, notify: Notify){
        user.followers.push({user, notify})
    }
    // 发布
    online(){
        this.status = "online"
        this.followers.forEach(({notify} => {
            notify(this)
        })
    }
}
test("should notify followers when user is online for multiple users", () => {
    const user1 = new User("user1")
    const user2 = new User("user2")
    const user3 = new User("user3")
    // 订阅者
    const mockNotifyUser1 = jest.fn()
    const mockNotifyUser2 = jest.fn()
    // 订阅user3
    user1.subscribe(user3, mockNotifyUser1)
    user2.subscribe(user3, mockNotifyUser2)
    // 发布,通知(执行)mockNotifyUser1和mockNotifyUser2
    user3.online()

    expect(mockNotifyUser1).toBeCalledWith(user3)
    expect(mockNotifyUser2).toBeCalledWith(user3)
})