前端设计模式应用

前端设计模式应用


什么是设计模式

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

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

设计模式背景

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

设计模式趋势
在这里插入图片描述
设计模式分类

23种设计模式

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

浏览器中的设计模式

单例模式

定义:
全局唯一访问对象

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

单例模式请求缓存

//定义api,500m后返回
import { api } from”./utils" ;

export cLass Requset {//定义请求实例
	static instance: Requset;//定义缓存
	private cache: Recordestring, string>;

	constructor() {//初始化缓存内容
		this.cache = {};
	}
	
	static getInstance() {
		if ( this. instance) {
			return this. instance;
		}
		
		this. instance = new Requset();
		return this. instance;
	}
	
	pubLic async request(url: string) { 
		if (this . cache[urU]){ 
			return this . cache[urL];
		}
		const response = await api(urL); 
		this . cache[urL] = response;
		
		return response;
	}
}
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 (urT: string){
		if (cache[urL]) {
			return cache[urL];
		}
	
	const response = await api(urL);
	
	cache[urL] = response;
	return response;
};

	test(" should response quickly second time", async 0) = { 
		await request(" /user/1");
		const startTime = Date. now() ;
		await request(" /user/1");
		const endTime = Date. now() ;
		
		const costTime = endTime - startTime;

		expect (costTime) . toBeLessThan(50) ;
});

发布订阅模式

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

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

type Notify = (user: User) => void;

export class User {
  name: string;
  status: "offline" | "online";
  // user 订阅自己的人,notify 上线时的通知函数
  followers: { user: User; notify: Notify }[];

  constructor(name: string) {
    this.name = name;
    this.status = "offline";
    this.followers = [];
  }

  // 订阅参数中的 user
  subscribe(user: User, notify: Notify) {
    user.followers.push({ user, notify });
  }

  // 上线
  online() {
    // 状态改为 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");

  // 通知 user1 和 user2 的函数
  const mockNotifyUser1 = jest.fn();
  const mockNotifyUser2 = jest.fn();

  // user1 订阅了 user3 的上线,传入通知 user1 的函数
  user1.subscribe(user3, mockNotifyUser1);
  user2.subscribe(user3, mockNotifyUser2);

  // user3 上线
  user3.online();

  // user3 会调用通知 user1 的函数
  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) => {
  // Object.create(obj) 会根据已有的对象返回一个新的对象
  // baseUser 是原型,与新创建的对象是继承关系
  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);
  //   });
  // }

  // online 只做上线一件事情,单一职责原则
  online() {
    this.status = 'online'
  }
}

// 实现通知
export const createProxyUser = (name: string) => {
  const user = new User(name);

  // 使用 new Proxy() 实现代理
  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;
};

迭代器模式

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

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

用 for of 迭代所有组件

// 浏览器中的 DOM 结构
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];
    let node;

    return {
      // for...of 迭代时调用的函数
      next: () => {
        while ((node = list.shift())) {
          // 层序遍历
          node.children.length > 0 && list.push(...node.children);
          // value 是迭代出的值,done 是指迭代是否完成
          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>

没有框架之前,我们通过监听 click 事件,通过 innerText 手动改变 DOM 的属性。前端框架对 DOM 操作进行代理,模板中的 DOM 都是 Proxy 代理后的虚拟 DOM,我们的操作的 DOM 是虚拟 DOM,之后通过 Diff 对视图更新。

在这里插入图片描述
组合模式

定义:
可多个对象组合使用成为一个单独的对象,也可以单个对象独立使用

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

总结

设计模式不是银弹。

  • 总结出抽象的设计模式比较简单,但是套用到场景中却非常困难。
  • 现代编程语言的多编程范式能带来更多的可能性。
  • 要从真正优秀的开源项目中学习设计模式并不断实现。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会思想的苇草i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值