# 什么是设计模式

设计模式是解决特定问题的经验总结和可复用的解决方案。可以提高代码的复用性、可维护性和可读性,是提高开发效率的重要手段。它有基本的五大原则可见 SOLID 原则在前端中的实践

下面以 JS 的角度介绍一些在前端中常用的设计模式。

# 单例模式

全局只允许创建一个实例的模式,在前端中常用于:

  • 全局状态管理
  • 浏览器 window 对象
  • 日志记录

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton {
constructor() {
if (!Singleton.instance) {
// 静态属性instance绑定首次传入的this
Singleton.instance = this;
}

return Singleton.instance;
}
}

const a = new Singleton();
const b = new Singleton();

console.log(a === b); // true

# 工厂模式

根据不同的参数创建不同对象的模式,通过工厂类来创建对象,将创建和使用分离。

例如我们想通过 JSON 数组来生成不同的表单,未使用工厂模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class TextInput {
constructor(name) {
this.name = name;
}
render() {
return `<div class="form-text"><input type="text" name="${this.name}""></div>`;
}
}

class CheckboxInput {
constructor(name) {
this.name = name;
}
render() {
return `<div class="form-checkbox"><input type="checkbox" name="${this.name}"></div>`;
}
}

function renderForm(formConfig) {
return formConfig.map(field => {
switch (field.type) {
case 'text':
return new TextInput(field.name).render();
case 'checkbox':
return new CheckboxInput(field.name).render();
}
}).join('');
}

const formConfig = [
{ type: 'text', name: 'username' },
{ type: 'checkbox', name: 'agree' },
];

// 将 formHTML 渲染到页面上
const formHTML = renderForm(formConfig);

上面的 renderForm 函数即负责了业务判断逻辑又负责了生成渲染,这违背了单一职责原则。

我们可以借助一个工厂类来负责判断逻辑,使外部函数只负责 UI 渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TextInput { /* ... 同上 ... */ }
class CheckboxInput { /* ... 同上 ... */ }

// 工厂类负责判断逻辑
class FormInputFactory {
createInput(fieldConfig) {
switch (fieldConfig.type) {
case 'text':
return new TextInput(fieldConfig.name);
case 'checkbox':
return new CheckboxInput(fieldConfig.name);
// ...更多新的类型
}
}
}

// 调用工厂函数后负责格式化标签渲染
function renderFormWithFactory(formConfig) {
const factory = new FormInputFactory();
return formConfig.map(field => factory.createInput(field).render()).join('');
}

const formConfig = [ /* ... 同上 ... */ ];

// 将 formHTMLWithFactory 渲染到页面上
const formHTMLWithFactory = renderFormWithFactory(formConfig);

# 观察者模式

一种对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动更新。在前端中常用于:

  • 添加事件监听
  • 发布 - 订阅事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Subject {
constructor() {
this.observers = [];
}

addObserver(observer) {
this.observers.push(observer);
}

removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}

notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}

class Observer {
constructor(name) {
this.name = name;
}

update(data) {
console.log(`${this.name}收到了${data}`);
}
}


const observer1 = new Observer('imtx');
const observer2 = new Observer('bubu');

const subject = new Subject();
subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify('hello world!');

# 装饰器模式

一种在不改变对象自身的基础上,动态地给对象增加新的功能的模式。在前端开发中,常用于实现组件的复用和功能的增强等。符合开放 / 封闭原则。

下面的代码通过装饰器不断新增标签特性,而非直接创建若干个子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class TextComponent {
constructor(text) {
this.text = text;
}
render() {
return this.text;
}
}

class TextDecorator {
constructor(component) {
this.component = component;
}
render() {
return this.component.render();
}
}

class BoldTextDecorator extends TextDecorator {
render() {
return `<strong>${super.render()}</strong>`;
}
}

class ItalicTextDecorator extends TextDecorator {
render() {
return `<em>${super.render()}</em>`;
}
}

const simpleText = new TextComponent("Hello");
console.log("Simple Text:", simpleText.render()); // 输出: Simple Text: Hello

const boldText = new BoldTextDecorator(simpleText);
console.log("Bold Text:", boldText.render()); // 输出: Bold Text: <strong>Hello</strong>

const italicBoldText = new ItalicTextDecorator(boldText);
console.log("Italic Bold Text:", italicBoldText.render()); // 输出: Italic Bold Text: <em><strong>Hello</strong></em>

const italicSimpleText = new ItalicTextDecorator(simpleText);
console.log("Italic Text:", italicSimpleText.render()); // 输出: Italic Text: <em>Hello</em>

const boldItalicText = new BoldTextDecorator(italicSimpleText);
console.log("Bold Italic Text:", boldItalicText.render()); // 输出: Bold Italic Text: <strong><em>Hello</em></strong>

# 代理模式

通过一个代理对象控制对目标对象的访问的模式。在前端常用于:

  • 图片懒加载
  • 数据缓存

在 ES6 提供了 Proxy 对象来实现代理模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
const handlerApiSummary = {
/**
* 拦截读取属性的操作。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要读取的属性名。
* @param {object} receiver - Proxy 实例或继承自 Proxy 的对象。
* @returns {*} - 被读取属性的值。
*/
get: function(target, property, receiver) {
// ... 实现拦截逻辑
},

/**
* 拦截设置属性值的操作。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要设置的属性名。
* @param {*} value - 要设置的新值。
* @param {object} receiver - Proxy 实例或继承自 Proxy 的对象。
* @returns {boolean} - 表示设置操作是否成功。
*/
set: function(target, property, value, receiver) {
// ... 实现拦截逻辑
},

/**
* 拦截 'in' 操作符和 Object.prototype.hasOwnProperty() 的调用。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要检查的属性名。
* @returns {boolean} - 表示目标对象是否拥有该属性。
*/
has: function(target, property) {
// ... 实现拦截逻辑
},

/**
* 拦截 delete 操作符。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要删除的属性名。
* @returns {boolean} - 表示删除操作是否成功。
*/
deleteProperty: function(target, property) {
// ... 实现拦截逻辑
},

/**
* 拦截函数调用操作。
* @param {function} target - 原始目标函数。
* @param {object} thisArg - 调用函数时的 'this' 上下文。
* @param {Array} argumentsList - 调用函数时传入的参数数组。
* @returns {*} - 函数调用的结果。
*/
apply: function(target, thisArg, argumentsList) {
// ... 实现拦截逻辑
},

/**
* 拦截 'new' 操作符。
* @param {function} target - 原始目标构造函数。
* @param {Array} argumentsList - 使用 'new' 调用构造函数时传入的参数数组。
* @param {function} newTarget - 最初被调用的构造函数。
* @returns {object} - 新的对象实例。
*/
construct: function(target, argumentsList, newTarget) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.getOwnPropertyDescriptor() 的调用。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要获取描述符的属性名。
* @returns {object | undefined} - 属性描述符对象或 undefined。
*/
getOwnPropertyDescriptor: function(target, property) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.defineProperty() 的调用。
* @param {object} target - 原始目标对象。
* @param {string | symbol} property - 要定义或修改的属性名。
* @param {object} descriptor - 要定义或修改的属性描述符对象。
* @returns {boolean} - 表示定义或修改操作是否成功。
*/
defineProperty: function(target, property, descriptor) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.getPrototypeOf() 的调用。
* @param {object} target - 原始目标对象。
* @returns {object | null} - 目标对象的原型对象。
*/
getPrototypeOf: function(target) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.setPrototypeOf() 的调用。
* @param {object} target - 原始目标对象。
* @param {object | null} prototype - 要设置的新原型对象。
* @returns {boolean} - 表示设置原型操作是否成功.
*/
setPrototypeOf: function(target, prototype) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 的调用。
* @param {object} target - 原始目标对象。
* @returns {Array<string | symbol>} - 包含目标对象自身拥有的所有属性名和符号的数组。
*/
ownKeys: function(target) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.preventExtensions() 的调用。
* @param {object} target - 原始目标对象。
* @returns {boolean} - 表示阻止扩展操作是否成功。
*/
preventExtensions: function(target) {
// ... 实现拦截逻辑
},

/**
* 拦截 Object.isExtensible() 的调用。
* @param {object} target - 原始目标对象。
* @returns {boolean} - 表示目标对象是否可扩展。
*/
isExtensible: function(target) {
// ... 实现拦截逻辑
}
};

// 创建一个使用了上述 handler API 结构的 Proxy (但没有实际逻辑)
const targetObject = {};
const proxyExample = new Proxy(targetObject, handlerApiSummary);

console.log("Proxy API Summary (No Actual Implementation):", proxyExample);

参考文章:
稀土掘金 - 前端必须掌握的 7 种设计模式

更新于

请我喝[茶]~( ̄▽ ̄)~*

imtangx 微信支付

微信支付

imtangx 支付宝

支付宝

imtangx 贝宝

贝宝