前端设计模式应用|笔记

46 阅读5分钟

前端设计模式应用

什么是设计模式

设计模式就是软件设计中常见问题的解决方案模型

  • 历史经验的总结
  • 与特定语言无关

设计模式背景

  • 两本书:

    1. 模式语言:城镇、建筑、建造(A Pattern Language: Towns, Buildings, Construction) 1977
    2. 设计模式:可复用面向对象软件的基础(Design Patterns: Elements of Reusable Object-Oriented Software) 1994

设计模式趋势

image-20241119194056827.png

image-20241120203631780.png

设计模式分类

23种设计模式

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

前端场景下的设计模式

浏览器API中的设计模式
单例模式
  • 定义:全局唯一访问对象

    image-20241119194805071.png

  • 应用场景:缓存,全局状态管理等

  • 用单例模式实现请求缓存

    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操作的代理

    image-20241119220846036.png

  • 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>
    

    image-20241120200639898.png

组合模式
  • 定义: 可多个对象组合使用,也可单个对象独立使用

  • 应用场景: 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>
        );
    }
    

设计模式不是银弹

  • 总结出抽象的模式相对比较简单,但是想要将抽象的模式套用到场景中却非常困难
  • 现代编程语言的多编程范式带来的更多可能性
  • 真正优秀的开源项目学习设计模式并不断实践