这是我参与「第四届青训营 」笔记创作活动的的第11天
前言
该篇文章主要介绍一些在前端中的设计模式及其应用,如单例模式、发布订阅模式、原型模式、代理模式等。
1. 概述
什么是设计模式
设计模式是软件设计中常见问题的解决模型:
- 是历史经验的总结
- 与特定语言无关,是实现某种需求的方法
设计模式趋势
设计模式分类
有23种设计模式分为:
- 创建型 - 如何创建一个对象
- 结构型 - 如何灵活的将对象组装成较大的结构
- 行为型 - 负责对象间的高效通信和职责划分
2. 浏览器API中的设计模式
2.1 单例模式
定义
- 全局唯一访问对象,在任何地方访问都会返回唯一对象,在任何地方修改都会修改该唯一对象
应用场景
- web应用中的全局缓存
- 如react、vue中的状态管理
举例
-
浏览器中的window对象
-
用单例模式实现请求缓存:用类实现
//用单例模式实现请求缓存
import {api} from '../utils/index'
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;
}
public async request(url:string){
if(this.cache[url]){
return this.cache[url];
}
const response = await api(url);
this.cache[url] = response;
return response;
}
}
Request类的instance属性存储全局唯一的Request 对象,cache 存储全局缓存。
Request类通过getInstance静态方法来获取全局唯一的 Request对象,通过request 公共方法来对请求进行包装,当这个url已经请求过就取cache中对应url的缓存数据。
测试:
- 用单例模式实现请求缓存:用方法实现
//用单例模式实现请求缓存
import {api} from '../utils/index'
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;
}
测试:
2.2 发布订阅模式(观察模式)
定义
- 一种订阅机制,可在被订阅对象发生变化时通知订阅者
应用场景
- 从系统架构之间的解耦(消息队列,redis缓存),到业务中一些实现模式,像邮件订阅,上线订阅等,应用广泛。
- 如vue中组件之间通信方式
举例
- 浏览器中的事件绑定
- 我们给按钮button绑定一个事件
click,点击按钮后就触发函数doSomthing... - 订阅对象就是按钮button,使订阅对象发生 变化的契机就是点击按钮,订阅者就是绑定的函数
doSomting..,点击按钮后订阅者就做一些事情。
- 我们给按钮button绑定一个事件
- 用发布订阅模式实现用户上线订阅
第一行的
Notify 是用来定义一个函数类型。
User类:
-
name属性定义创建对象时,对象的名称。 -
status属性用于表示该用户是否上线:offline下线,online上线 -
followers:用于存储订阅该对象的数组,数组中存储订阅该对象的对象以及传入的函数。
构造函数用于完成对象的初始化。
-
subscribe函数用于完成订阅对象这个行为,user参数传入要订阅的对象,notify传入函数,然后放入被订阅者的followers数组中。 -
online函数用来执行对象上线的动作,并通知订阅者,执行订阅的函数。
测试:
创建三个对象user1 user2 user3 ,user1 user2 去订阅user3,传入执行函数。user3上线就执行订阅者user1 user2的传入的函数。
3. JavaScript中的设计模式
3.1 原型模式
定义
- 复制已有对象来创建新的对象
应用场景
- JavaScript中对象创建的基本模式
举例
用原型模式创建上线订阅中的用户
我们通过对象字面量来存储
User对象的相关属性和方法。通过createUser方法来创建User对象,通过该方法中的Object,create,会基于已有对象来返回一个新的对象
测试
3.2 代理模式
定义
- 可自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理
应用场景
- 监控,代理工具,前端框架实现(vue中的数据劫持,)等等。
举例
使用代理模式实现用户状态订阅:
-
我们上边的例子中
online函数做了两件事情,一是用户上线,二是通知订阅者。 -
我们在实际开发中应尽量满足一个函数只做一件事,那么我们可以通过代理模式来实现通知订阅者,而
online函数只进行对象上线操作,如下图。
实现真正的通知订阅者行为:
-
通过
createProxyUser来创建User的代理对象,通过创建Proxy,传入被代理对象user,以及传入需要进行的操作,有两个函数get,set,get用于获取user对象时进行一些操作,set用户设置user对象时进行一些操作,这里我们只需要set。 -
set函数参数target指代被代理对象user,prop指定被代理对象的属性,value表示设置的值。 -
这个
set函数做的事情是,当对被代理对象中的属性赋值时,先进行赋值操作(this.status = "online"),再判断该prop属性是不是status,如果是就执行notifyStatusHandlers函数;该函数会判断传入值value是不是online,如果是就通知订阅者该用户已上线。
3.3 迭代器模式
定义
- 在不暴露数据类型的情况下访问集合中的数据
应用场景
- 数据结构中有多种数据类型,列表、数等,提供通用操作接口。
举例
-
JavaScript中的
for of -
用
for of迭代所有组件
创建一个MyDomElement类,tag属性表示组件名称,children表示承载该组件的所有子组件数组,addChildren用来添加子组件。
通过Symbol.iterator方法将对象变成可迭代对象;这个方法返回一个对象,对象中有一个函数next ,会遍历出所有的子组件,进行返回;返回值是一个对象,有两个参数:vaule表示返回的值,done表示是否迭代完成。
测试
4. 前端框架中的设计模式
主要介绍代理模式和组合模式
4.1 代理模式
举例
Vue 组件实现计数器
这是实现的代码,通过点击按钮,使
count+1,但是为什么页面会自动更新,不应该进行DOM操作吗,这就是我们要讲的前端框架中对DOM操作的代理。
先看有框架前后的视图更新我们是怎么做的:
-
前:当我们更新DOM属性
count时,我们通过innerText来更新视图中的count值。 -
后:当我们更新DOM属性
count时,先有框架对虚拟DOM更新,然后通过Diff算法比对何处变化,由框架来完成视图的更新,我们只需要更新数据,就会自动更新视图。
DOM更新前后的钩子:当我们点击按钮count值+1时,就会输出更新前后的值
4.2. 组合模式
定义
- 可多个对象组合使用,也可单个对象独立使用
应用场景
- DOM结构,前端组件(多个组件组成一个页面),文件目录,部门
举例
React的组件结构:
调用setCount时,设计一个队列对count进行主动更新,也是对DOM做代理,但不是JavaScript中的Proxy。
Count组件可以作为一个独立的组件进行渲染,也可以作为一个组件的一部分。
5. 总结
- 总结出抽象的模式相对比较简单,但是想要将抽象的模式套用到场景中非常困难。
- 现代编程语言的多编程范式带来的更多可能性,响应式编程,函数式编程。
- 可以通过优秀的开源项目学习设计模式并不断实践。
\