前端设计模式应用 | 青训营笔记

60 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

一、本堂课重点内容:

单例模式,发布者订阅者,原型模式,代理模式,迭代器模式,组合模式。

二、详细知识点介绍:

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

  • 设计模式三大分类:

    • 创建型模式:关注如何创建一个对象

      单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式;

    • 结构型模式:关注如何灵活的将对象组装成较大的结构

      适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

    • 行为型模式:负责对象间的高效通信和职责划分

      模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

  • 浏览器中的设计模式

    • 单例模式

      定义:全局唯一访问对象,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

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

    • 发布者订阅者模式

      定义:可在被订阅对象发生变化时通知订阅者

      应用场景:从系统架构之间的解耦,到业务中一些实现模式,eg. 邮件订阅,上线订阅等

  • Javascripth中的设计模式

    • 原型模式

      定义:复制已有对象来创建新的对象

      应用场景:JS中对象创建的基本模式

    • 代理模式

      定义:可自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理

      应用场景:监控,代理工具,前端框架实现

    • 迭代器模式

      定义:在不暴露数据类型的情况下访问集合中的数据

      应用场景:数据结构中有多种数据类型,列表,树等,提供通用操作接口

  • 前端框架中的设计模式

    • 代理模式

      直接访问会给使用者或者系统结构带来很多麻烦(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),我们可以在访问对象时加上一个对此对象的访问层。

      eg. 框架中对DOM操作的代理:

      image.png

    • 组合模式

      将对象组合成树形结构以表示"部分-整体"的层次结构。 eg. React的组件结构,count组件既能独立渲染,又能在App中渲染,

      function App() {
          return (
              <div className="App">
                  <Header />
                  <Count />
                  <Footer />
              </div>
          );
       }
      

三、实践练习例子:

  1. 用单例模式实现请求缓存

    即对于相同的情节路径,第二次发送请求时复用上一次请求的某些值。

       import { api } from "./utils";
       
       // 创建Request类
       export class Request {
           static instance: Request;
           private cache: Record<string, string>;
           
           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;
           }
           
           
       }
    
  2. 用发布订阅模式实现用户上线订阅

    type Notify = (user: User) => void;
    
    // 创建user类
    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);
            });
        }
    }
    
    
  3. 用原型模式创建上线订阅中的用户

    // 原型
     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;
     };
    
    
  4. 用代理模式实现用户状态订阅

    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);
            });
        }
    }
    
    
  5. 用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]; //list 队列
            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 };
                },
            };
        }
    }
    
    
  6. Vue组件实现计数器

    <template>
    <button @click="count++">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>
    

四、课后个人总结:

设计模式有很多种,只是听听看看是不可能掌握的,要在实际开发过程中想办法套用设计模式,它带给我们的更多的是一种编码思路,帮助我们写出可维护性较好的代码。

设计模式是前辈们在大量开发总积累的经验,在问题面前,重要的不是是否使用了某个设计模式,而是你是否能将这些模式内化,将模式的思想转化为代码。

关于设计模式的学习还有很长一段路要走。

五、引用参考

前端设计模式应用-吴立宁老师

设计模式学习总结