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

70 阅读6分钟

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

什么是设计模式

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

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

设计模式背景

1.模式语言:城镇、建筑、建造(A Pattern Language: Towns, Buildings,Construction)1977

2.设计模式:可复用面向对象软件的基础(Design Patterns: Elements of ReusableObject-Oriented Software) 1994

设计模式趋势

image.png

设计模式分类

创建型–如何创建一个对象(高效性能,灵活使用)

结构型-如何灵活的将对象组装成较大的结构(创建好的对象灵活组装)

行为型-负责对象间的高效通信和职责划分(做事先后、做事分配)

浏览器中的设计模式

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

单例模式

定义:全局唯一访问对象

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

用单例模式实现请求缓存

import { api } from "./utils";
//定义一个api
export class Requset {
    static instance: Requset;
    //静态方法instance,存储全局唯一实例Request
    private cache: Record<string, string>;
    //缓存对象
    constructor(){
        this.cache = {};
        //初始化缓存内容
     }
    static getInstance() {//不用new request创建对象,使用getInstance创建对象
        if (this.instance) {
            return this.instance;
            //如果instance存在就返回该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;
        //调用api,写入缓存,返回api值
    }
}

无缓存与有缓存测试

image.png

//javascript
import { api } from "./utils";
const cache: Record<string,string> = {};
//定义空缓存(区分java、c++需要new一个cache)
export const request = async (url: string)=>{
    if (cache[url]) {
        return cache[url] ;
    }
    const responeo =await api(url);
    cache[url] = response;
    return response;
};

js测试

image.png

发布订阅模式

定义:一种订阅机制,可在被订阅对象发生变化时通知订阅者。

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

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

type Notify = (user: User)=>void;
//类型定义,订阅者被通知的函数
export class User {
    name: string;
    status:"offline"| "online";
    followers: { user:User ; notify: Notify }[];
    //订阅者,上线时需要通知的函数
    constructor(name : string) {
    //初始化一个新的User
        this.name = nane;
        this.status = "offline";
        this.followers = [];
        //初始化状态为offline,初始化被订阅的人的数组
    }
    subscribe(user : User, notifu: Notify){
    //订阅方法,需要订阅的人,订阅的人上线时的通知函数
        user.followers. push({ user , notify });
        //给书籍对应的订阅的用户添加自己和对应的订阅函数
    }
    online(){
        this.status = "online ;
        this.followers.forEach({ notify })=> {   
            notify(this);
        });
    }
}

image.png

用户一、二订阅了用户三,用户三上线后被传给用户一、二

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;
}

image.png

代理模式

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

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

使用代理模式实现用户状态订阅

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.folLlowers.push( {user, notify H});
    }
    online() {
        this.status = "online" ;
        //用户状态上线
        this.followers.forEach(({ notify })=>{
            notify(this);
        });
        //通知关注的人,用户上线提示
    }//复杂不好维护,不好添加更多功能
}

//代理模式优化
export const createProxyUser =(name: string)=>{
//创建函数,接收name参数
    const user = new User(name);
    //正常user
    const proxyUser= new Proxy(user,{
    //创建代理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触发对应事件
               notify(user);
           });
       }
   };
   return proxyUser;
};

image.png

迭代器模式

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

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

image.png

用for..of..方式拿到所有数据

用for of 迭代所有组件

class MyDomElement {//模拟Dom结构
    tag: string;//将要传入的标签
    children: MyDomElement[];//对应的children(子组件)
    constructor(tag: string) {
        this.tag = tag;
        this.children = [];
    }
    addChildren(component: MyDomElement) {
        this.children.push(component);
    }
    [Symbol.iterator](){//内置方法,试\使组件可迭代
        const list = [...this.children];//自己的子组件
        let node;
        return {//返回next()函数,for of调用的迭代函数
            next: ()=>while ((node = list.shift())){//每个子组件独立node,如果这个node有children就把它push到整体的list上(先序遍历)
                node.children.length > 0 && list.push( ...node.children);
                return { value: node,done: false };//返回value迭代出来的值,还有done是否完成--遍历返回数据
            }
            return { value: null,done: true };
        },
    };
}

image.png

body下面有三个组件,body&head&footer.main下面有两个组件banner和content.for去遍历body的树状结构

前端框架中的设计模式

  • 代理模式
  • 组合模式

不同于js中api提供的代理模式

代理模式

Vue组件实现计数器

<template> 
    <button @click="count++ ">count is: {{ count }}</button></template>
    //button定义,监听click函数,变量count累加1,显示count的值
<script setup lang="ts">
    import { ref } from "vue" ;
    const count = ref(0);
    //从vue中引入一个ref
    //定义变量初始值为零ref(0),返回count新对象
</ script>

前端框架中对DOM操作的代理

image.png

更新被代理过后的Dom

Dom更新前后的钩子

image.png

组合模式

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

应用场景: DOM,前端组件,文件目录,部门

React的组件结构

export const Count = ()=>{
    const [count,setCount] = useState(0);
    return (
        <button onClick={()=>setCount((count) =>count + 1)}>
            count is: {count}
        </button>
    );
};

所有的count都不需要自己去调用操作Dom,自动完成.调用setCount的时候对创建一个队列做主动更新,也是对Dom的代理

image.png

count可以被独立渲染,也可以在更大的结构里被渲染,可以将整个App渲染
总结

总结出抽象的模式相对比较简单,但是想要将抽象的模式套用到场景中却非常困难

现代编程语言的多编程范式带来的更多可能性

练习题

image.png