详解前端框架中的设计模式,并对比分析优缺点以及使用案例 | 青训营

290 阅读4分钟

1 前端框架设计模式

1.1 什么是设计模式?

设计模式就是戳软件设计开发过程中反复出现的某类问题的通用解决方案。
软件设计中常见问题的解决方案模型:历史经验总结;与特定语言无关
3种设计模式:

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

浏览器中的设计模式:

  • 单例模式:
    • 定义:全局唯一访问对象
    • 应用场景:缓存,全局状态管理等
    • 举例:
import {api} from '../utils'
export class Requset{
  static instance: Requset;
  private cache: Record<string, string>;
  isConstructorDeclaration(){
    this.cache = {}
  }
  static getInstance(){
    if(this.instance){
      return this.instance
    }

    this.instance = new Requset()
    return this.instance
  }
}

public async request(url:string){
  if(this.cache[url]){
    return this.cache[url]
  }
  const response = await api(url)
  this.cache[url] = response
  return response
}

test("Should response more than 500ms with class", async () =>{
  const request = Requset.getInstance()
  const startTime = Date.now()
  await request.request('/user/1')
  const endTime = Date.now()

  const costTime = endTime - startTime

  isExportDeclaration(costTime).toBeGreaterThanOrEqual(500)
})

test("Shoule response quickly second time with class", async()=>{
  const request1 = Requset.getInstance()
  await request1.request('/user/1')
  const startTime = Date.now()
  const request2 = Requset.getInstance()
  await request2.request('/user/2')
  const endTime = Date.now()

  const costTime = endTime - startTime

  expect(costTime).toBeLessThan(50)
})
  • 发布订阅模式:
    • 定义:一种订阅机制,可在被订阅对象发生变化时通知订阅者(订阅者指的是一个函数)
    • 应用场景:从系统架构之间的解耦,到业务中一些实现模式,像邮件订阅,上线订阅等等,应用广泛。
const button = document.getElementById('button')
const doSomthing1 = () =>{
  console.log("Send message to user")
}
const doSomthing2 = ()=>{
  console.log('Log...')
}
button.addEventListener("click", doSomthing1)
button.addEventListener("click", doSomthing2)

1.2 JavaScript种的设计模式

  • 原型模式
    • 定义:复制已有对象来创建新的对象
    • 应用场景:JS中对象创建的基本模式
// 用原型模式创建上线订阅中的用户
const baseUser: User = {
  name: '',
  status: 'offline',
  followers: [],
  subscribe(user, notify) {
    user.followers.push({ user, notify })
  },
  online() {
    this.status = 'online'
    this.followers.forEach(({ notify }) => {
      notify(this)
    })
  },
}
export const createUser = (name: string) => {
  const user: User = Object.create(baseUser)
  user.name = name
  user.followers = []

  return user
}

test('shoule notify followers when user is online for user prototypes', () => {
  const user1 = createUser('user1')
  const user2 = createUser('user2')
  const user3 = createUser('user3')

  const mockNotifyUser1 = jest.fn()
  const mockNotifyUser2 = jest.fn()

  user1.subscribe(user3, mockNotifyUser1)
  user2.subscribe(user3, mockNotifyUser2)

  user3.online()

  expect(mockNotifyUser1).toBeCalledWith(user3)
  expect(mockNotifyUser2).toBeCalledWith(user3)
})
  • 代理模式
    • 定义:可自定义控制原对象的访问方式,并且允许在更新前后做一些额外处理
    • 应用场景:监控,代理工具,前端框架实现等等
  • 迭代器模式
    • 定义:不暴露数据类型的情况下访问集合中的数据
    • 应用场景:数据结构中有多种数据类型、列表、树等,提供通用操作接口
const numbers = [1,2,3]
const map = new Map()
map.set("k1", "v1")
map.set("k2", 'v2')

const set = new Set(['1','2','3'])
for(const number of numbers){

}
for(const [key, value] of map){

}
for(const key of set){

}
// 用for of 迭代所有组件
class MyDomElement {
  tag: string
  children: MyDomElement[]
  constructor(tag: string) {
    this.tag = tag
    this.children = []
  }

  addChildren(component: MyDomElement) {
    this.children.push(component)
  }
  [Symbol.iterator]() {
    const list = [...this.children]
    let node

    return {
        // 返回一个对象,对象返回一个value和done
      next: () => {
        while ((node = list.shift())) {
          node.children.length > 0 && list.push(...node.children)
          return { value: node, done: false }
        }
        return { value: null, done: true }
      },
    }
  }

eg.Vue组件实现计数器

<template>
  <button @click="count++">count is: {{ count }}</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>

<style lang="scss">
button {
  margin: 20px;
}
</style>

image.png

前端框架中对DOM操作的代理: image.png
先更新虚拟DOM,进行视图更新。

  • 组合模式
    • 定义:可多个对象组合使用,也可单个对象独立使用
    • 应用场景:DOM、前端组件、文件目录、部门

2 使用案例并分析——中介者模式(Mediator Pattern)

这里使用了我觉得感兴趣的模式进行案例分析——中介者模式。参考:zhuanlan.zhihu.com/p/133263261

image.png
在中介者模式中,中介者(Mediator)包装了一系列对象相互作用得方式,使得这些对象不必直接相互作用,而是由中介者协调它们之间的交互,从而使它们可以松散耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用,保证这些作用可以彼此独立的变化。
中介者模式和观察者模式有一定的相似性,都是一对多的关系,也都是集中式通信,不同的是中介者模式是处理同级对象之间的交互,而观察者模式是处理Observer和Subject之间的交互。我认为该作者打比方非常有趣,“中介者模式有点像婚恋中介,相亲对象刚开始并不能直接交流,而是要通过中介去筛选匹配再决定谁和谁见面。”中介者模式比较常见的应用比如聊天室,聊天室里面的人之间并不能直接对话,而是用过聊天室这一媒介进行转发。

  • 简易的聊天室模型如下:
function Member(name) {
  this.name = name
  this.chatroom = this.chatroom
}

Member.prototype = {
  // 发送消息
  send(message, toMember) {
    this.chatroom.send(message, this, toMember)
  },
  // 接收消息
  receive(message, fromMember) {
    console.log(`${fromMember.name} to ${this.name}: ${message}`)
  },
}

function Chatroom() {
  this.members = {}
}

Chatroom.prototype = {
  // 增加成员
  addMember(member) {
    this.members[member.name] = member
    member.chatroom = this
  },
  // 发送消息
  send(message, fromMember, toMember) {
    toMember.receive(message, fromMember)
  },
}

const chatroom = new Chatroom()
const bruce = new Member('bruce')
const jonas = new Member('jonas')

chatroom.addMember(bruce)
chatroom.addMember(jonas)

bruce.send('Hey jonas', jonas)
//bruce to jonas: Hey jonas

总结

除了课程中的设计模式外,还有很多其他设计模式。课程中讲到要去学习真正优秀的开源项目,并拿来学习设计模式并实践,要记住模式思想,而非记住固定结构,其结构不是固定的。通过学习和时间不断开拓自己的编码思维,在这个过程中不断成长,提高代码质量。
深入学习JavaScript以及后面的知识后,发现真的非常有趣,尤其是实现生活中常见的小玩意。