历史背景
1995年,4位面向对象领域的前辈著作《设计模式:可复用面向对象软件基础》中提出了23种设计模式,总结出的固定的编码套路,在编程界达成共识,成为了标准的沟通语言,后续从业人员在软件编写过程中直接套用即可。
当时还没有前端的概念,并非所有模式都适合前端开发。
前端常见的7种设计模式
前端常用的模式:工厂模式、单例模式、观察者模式、迭代器模式、原型模式、装饰器模式、代理模式。
1、工厂模式
创建对象的一种方式,不用每次都亲自创建对象,而是通过一个既定的“工厂”来生产对象。
普通方法:
function Food(a, b) {
// 、、、、
}
let food
if (a) {
food = new Food(x);
}
if (b) {
food = new Food(x, y);
}
工厂方法:
function Food(a, b) {
// 、、、、
}
function createFood(a, b) {
if (a) {
food = new Food(x);
}
if (b) {
food = new Food(x, y);
}
}
const food1 = createFood(a);
const food2 = createFood(a, b);
优点
- 把逻辑判断封装到工厂函数中,只需传入不同的参数,获得不同的结果;
- 符合开放封闭原则;
- 代码复用性好;
场景
- jQuery:将逻辑用工厂函数封装起来,挂载到window.('.div').addClass('abc')
class JQuery {
selector: string
length: number
constructor(selector: string) {
const domList = Array.from(document.querySelectorAll(selector));
const length = domList.length;
for (let index = 0; index < length; index++) {
this[index] = domList[index];
}
this.selector = selector;
this.length = length;
}
append(ele: HTMLElement) {
// 、、、
return this;
}
}
function $(selector: string) {
return new JQuery(selector);
}
window.$ = $;
- vue中render函数中生成vnode的方法:_createElementVNode()
- React.createElement
2、单例模式
一个系统里只能有一个实例,且只能被创建一次,并缓存起来,一直使用。
全局唯一的思想。
function getSingleTon() {
let instance;
class SingleTon { }
return () => {
if (!instance) {
instance = new SingleTon ();
}
return instance;
}
}
const instance = getSingleTon();
const abc = instance();
const acd = instance();
console.log(abc === acd); // true
优点
- 节约性能,精简代码
- 数据都在一个实例里,便于数据通信,数据流清晰
场景
-
Vuex中store
-
eventBus
3、观察者模式
一开始监听后就不管了,等到观察目标有了行为后,通知到观察者,然后做出对应的反应。无需实时监听,耗费性能。
UI交互层面几乎都是观察者模式。
class Subject {
private state: number = 0
private observers: Observer[] = []
getState() {
return this.state
}
setState(newState: number) {
this.state = newState
this . notify ()
}
addObserver(observer: Observer) {
this.observers.push(observer)
}
private notify() {
this.observers.forEach(observer => {
observer.update(this.state)
})
}
}
class Observer {
name: string = ''
constructor(name: string) {
this.name = name
}
update(state: number) {
console.log(this.name)
console.log(state)
}
}
const sub = new Subject()
const observer = new Observer('abc')
sub.addObserver(observer)
sub.setState(8)
场景
- dom事件:绑定好了只待触发
- vue生命周期:钩子函数
- vue的watch
- 异步回调:setTimeout、Promise.then
- mutationObserver
观察者模式 vs 发布订阅模式
观察者模式:观察者和发布者直接关联,如addEventListener
发布订阅模式:观察者和发布者互不认识,中间有媒介,如event自定义事件,需要eventbus或mitt作为媒介
注意事项
自定义事件注意解绑,防止内存泄漏
4、迭代器模式
对有序结构和有序数据更优雅、更符合设计原则的遍历。
参考:es6.ruanyifeng.com/#docs/itera…
普通for循环的缺点:
for (let index = 0; index < array. length; index++) { // 暴露长度
const element = array[index]; // 必须知道索引才能获取对应的值
}
不完全的迭代器:
const dom = document.querySelectorAll('div')
dom.forEach((item) => console.log(item)) // 无需太多内部信息即可获取item
简单的迭代器实现:
class MyInterator {
data = []
index = 0
constructor(data) {
this.data = data;
}
next() {
if (this.hasNext()) {
return this.data[this.index++];
}
return null;
}
hasNext() {
if (this.index >= this.data.length) return false;
return true;
}
}
const content = new MyInterator([1, 2, 3, 4, 5, 6]);
while (content.hasNext()) {
const num = content.next();
console.log(num);
}
js有序对象内置迭代器Symbol.interator方法:字符串、数组、NodeList、Map、Set、arguements。
const arr = [1, 4, 5, 2]
const interator = arr[Symbol.iterator]()
interator.next()
作用
1、用于for...of遍历内置的interator
2、数组解构、扩展操作符、Array.form
3、用于Promise.all()、Promise.race()
5、原型模式(不常用)
复制一个已创建的实例,来创建一个和原实例相似的新对象。
场景
Object.create指定原型
const obj2 = Object.create({x: 100}) obj2.proto // {x:100}
6、装饰器模式
向一个现有的对象添加新的功能,同时又不改变其结构和原有功能,属于结构型模式,它是作为现有的类的一个包装,动态地给一个对象添加一些额外的职责。
一个功能外挂,功能扩展。
class Circle {
draw() {
console.log('画一个圆')
}
}
class Decorator {
private circle: Circle
constructor(circle: Circle) {
this.circle = circle
}
draw() {
this.circle.draw() // 不改变原有方法
this.setBorder() // 装饰的内容,扩展方法,对扩展开放
}
private setBorder() {
console.log('设置边框颜色')
}
}
const circle = new Circle()
circle.draw()
const decorator = new Decorator(circle)
decorator.draw()
es6写法
function testName(target) { // 装饰器
// target Foo对象
target.abc = true;
}
@testName // 将对目标作为对象传入装饰器
class Foo {
static abc
}
7、代理模式
为其他对象提供一种代理以控制对这个对象的访问,用户不得直接访问对象。
提供一个访问对象的中间层、代理方,会更加方便或避免直接访问引起的一些副作用。
class RealImg {
fileName
constructor(fileName) {
this.fileName = fileName
this.loadFromDist()
}
display() {
this.loadFromDist();
console.log('display...', this.fileName)
}
private loadFromDist() {
console.log('loading...', this.fileName)
}
}
class ProxyImg {
readImg: RealImg
constructor(fileName) {
this.readImg = new RealImg(fileName)
}
display() {
// 做一些限制或判断等处理
this.readImg.display()
}
}
const proxImg = new ProxyImg('xxx.png') // 使用代理
proxImg.display()
场景
1、DOM事件代理(委托)
2、webpack devServer代理
3、Nginx反向代理
4、vue3 proxy 追踪属性访问