前端设计模式应用
什么是设计模式
设计模式就是软件设计中常见问题的解决方案模型
- 历史经验的总结
- 与特定语言无关
设计模式背景
-
两本书:
- 模式语言:城镇、建筑、建造(A Pattern Language: Towns, Buildings, Construction) 1977
- 设计模式:可复用面向对象软件的基础(Design Patterns: Elements of Reusable Object-Oriented Software) 1994
设计模式趋势
设计模式分类
23种设计模式
- 创建型-如何创建一个对象
- 结构型-如何灵活的将对象组装成较大的结构
- 行为型-负责对象间的高效通信和职责划分
前端场景下的设计模式
浏览器API中的设计模式
单例模式
-
定义:全局唯一访问对象
-
应用场景:缓存,全局状态管理等
-
用单例模式实现请求缓存
import { api } from "./utils"; export class Requset { static instance: Requset; private cache: Record<string, string>; constructor() { this.cache = {}; } //如果instance存在就返回自身 没有就新建 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; } }//两个keys //没有缓存 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; expect(costTime).toBeGreaterThanOrEqual(500); }); //有缓存 test("should 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/1"); const endTime = Date.now(); const costTime = endTime - startTime; expect(costTime).toBeLessThan(50); });或者
import { api } from "./utils"; const cache: Record<string, string> = {}; export const request = async (url: string) => { if (cache[url]) { return cache[url]; } const response = await api(url); cache[url] = response; return response; };test("should response quickly second time", async () =>{ await request("/user/1"); const startTime = Date.now(); await request("/user/1"); 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); //... -
用发布订阅模式实现用户上线订阅
type Notify = (user: User) = void; //用户的定义 export class User { name: string; status: "offline" | "online"; followers: { user: User; notify: Notify }[]; //初始化 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(); //订阅者 user1.subscribe(user3, mockNotifyUser1); user2.subscribe(user3, mockNotifyUser2); //发布者 user3.online(); expect(mockNotifyUser1).toBeCalledWith(user3); expect(mockNotifyUser2).toBeCalledWith(user3); });
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("should notify followers when user is online for multiple users", () => { 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); });
代理模式
-
定义:可自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理
-
应用场景:监控,代理工具,前端框架实现等
-
用代理模式创建上线订阅中的用户
type Notify = (user: User) = void; export class User { name: string; status: "offline" | "online"; followers: { user: User; notify: Notify }[]; 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); }); } }优化
type Notify = (user: User) = void; export class User { name: string; status: "offline" | "online"; followers: { user: User; notify: Notify }[]; 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"; } }export const createProxyUser = (name: string) => { const user = new User(name); const proxyUser = new Proxy(user, { set: (target, prop: keyof User, value) = { target[prop] = value; if (prop === "status") { notifyStatusHandlers(target, value); } return true; }, }); const notifyStatusHandlers = (user: User, status: "online" | "offline") => { if (status === "online") { user.followers.forEach(({ notify }) => { notify(user); }); } }; return proxyUser; };
迭代器模式
-
定义:在不暴露数据类型的情况下访问集合中的数据
-
应用场景:数据结构中有多种数据类型,列表,树等,提供通用操作接口
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); } //JavaScript内置方法:可以让组件变成可迭代的 [Symbol.iterator] () { const list = [ ... this.children]; let node; return { next: () => { while ((node = list.shift())) { node.children.length > 0 && list.push( ... node.children); return { value: node, done: false }; } return { value: null, done: true }; }, }; } }test("can iterate root element", () => { const body = new MyDomElement("body"); const header = new MyDomElement("header"); const main = new MyDomElement("main"); const banner = new MyDomElement("banner"); const content = new MyDomElement("content"); const footer = new MyDomElement("footer"); body.addChildren(header); body.addChildren(main); body.addChildren(footer); main.addChildren(banner); main.addChildren(content); const expectTags: string[] = []; for (const element of body) { if (element) { expectTags.push(element.tag); } } expect(expectTags.length).toBe(5); });
-
前端框架中的设计模式
代理模式
-
Vue组件实现计数器
<template> <button @click="count++">count is: {{ count }}</button> </template> <script setup lang="ts"> import { ref } from "vue"; const count = ref(0); </script> -
前端框架中对DOM操作的代理
-
Dom更新前后的钩子
<template> <button @click="count++" ref="dom">count is: {{ count }}</button> </template> <script setup lang="ts"> import { ref, onBeforeUpdate, onUpdated } from "vue"; const count = ref(0); const dom = ref<HTMLButtonElement>(); onBeforeUpdate (() = { console.log("Dom before update", dom.value ?. innerText) ; } onUpdated(() = { console. log("Dom after update", dom.value ?. innerText); } </script>
组合模式
-
定义: 可多个对象组合使用,也可单个对象独立使用
-
应用场景: DOM, 前端组件, 文件目录, 部门
-
React 的组件结构
export const Count = () = { const [count, setCount] = useState(0); return( <button onClick={() = setCount((count) = count + 1)}> count is: {count} </button> ); };function App() { return ( <div className="App"> <Header /> <Count /> <Footer /> </div> ); }
设计模式不是银弹
- 总结出抽象的模式相对比较简单,但是想要将抽象的模式套用到场景中却非常困难
- 现代编程语言的多编程范式带来的更多可能性
- 真正优秀的开源项目学习设计模式并不断实践