前端应用设计思维提升|青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第6天!
浏览器中的设计模式
单例模式
- 定义:全局唯一访问对象
- 应用场景:缓存、全局状态管理等。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
需要注意的是: - 单例类只能有一个实例。
- 单例类必须给自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一个实例。
优点是:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用。
缺点是:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用单例模式实现请求缓存代码如下:
import { http } from './utils';
const cache: Record<string, string> = {};
//定义缓存变量
export const request = async (url: string) => {
//封装一个异步请求
if (cache[url]) {
return cache[url];
}
const response = await http.get(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();
expect(endTime - startTime).toBeLessThan(50);
})
发布订阅模式
- 定义:一种订阅机制,可在被订阅对象发生变化时通知订阅者。
- 应用场景:从系统架构之间的解耦,到业务中一些实现模式,像邮件订阅、线上订阅等等,应用广泛。
发布—订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
优点:耦合性低,便于代码的维护
缺点:创建订阅者本身要消耗一定的时间和内存,可能订阅的消息未发生,但这个订阅者会始终存在于内存中 使用发布订阅模式实现请求缓存代码如下:
type Notify = ( uesr: User ) => void;
export class User {
name: string;
status: "online" | "offline";
followers: { user: User, notify: Notify }[];
constructor(name: string) {
this.name = name;
this.status = "online";
this.followers = [];
}
subscribe(user: User, notify: Notify) {
user.followers.push({ user, notify })
}
online() {
this.status = "online";
this.followers.forEach(({ notify }) => notify(this));
}
}
JavaScript中的设计模式
JavaScript中的设计模式有三种:
- 原型模式
- 代理模式
- 迭代器模式。
原型模式
- 定义:复制已有对象未创建新的对象。
- 应用场景:JS中对象创建的基本模式。
优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用,可辅助实现撤销操作。
缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。
使用原型模式创建上线订阅中的用户代码如下:
const baseUser: User = {
name: "",
status: "offline",
followers: [],
subscribe(user: User, notify: 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;
return user;
}
代理模式
- 定义:可自定义控制原对象的访问方式,并且允许在更新前后做一些额外处理。
- 应用场景:监控、代理工具、前端框架实现等。
代理模式特点:自定义控制对元的对象访问,并且允许更新前后处理;多用于监控和代理等。
使用代理模式创建上线订阅中的用户代码如下:
type Notify = (uesr: User) => void;
export class User {
name: string;
status: "online" | "offline";
followers: { user: User, notify: Notify }[];
constructor(name: string) {
this.name = name;
this.status = "online";
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;
}
迭代模式
-
定义:在不暴露数据类型的情况下访问集合中的数据
-
应用场景:数据结构中有多种数据类型,列表、树等,提供通用操作接口。
迭代器模式的本质:控制访问聚合对象中的元素。其设计意图:无须暴露聚合对象的内部实现,就能够访问到聚合对象中的各个元素。 迭代模式可以实现隐藏数据类型的访问。适用于复杂数据结构的解析,提供通用操作接口。 常见的有数组、列表、树的混用。
前端框架中的设计模式
组合模式
- 定义:可对多个对象组合使用,可也单个对象独立使用。
- 应用场景:DOM、前端组件、文件目录、部门。
组合多个对象形成树形结构以表示“整体-部分”的关系的层次结构。组合模式对叶子节点和容器节点的处理具有一致性,又称为整体-部分模式。
优点:
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
- 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
- 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
- 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
缺点:
- 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联
个人总结
通过这次课程我学习到了前端设计模式,这个对我个人来说是一个全新的知识,本次课程让我对前端的系统理论有了新的认识,认知到自身的不足,需要努力学习提高自身。