前端常用设计模式


上次更新时间:3/14/2021, 1:35:34 PM 0

# 工厂模式(必用)

  • 将 new 单独封装
  • 遇到 new 时,就要考虑是否该使用工厂模式

使用场景:jQuery的封装、以函数的形式导出模块。

class jQuery {
    constructor(selector) {
    }
    addClass(name) {
    }
}
// jQuery 的 封装
window.$ = function(selector) {
    return new jQuery(selector)
}
// 使用不需要 new jQuery()
var $p = $('p')
1
2
3
4
5
6
7
8
9
10
11
12
class Popup {
    // ...
}
// 以函数的形式导出模块
function popup (options: Ipopup) {
    return new Popup(options);
}
export default popup;
1
2
3
4
5
6
7
8

设计模式验证

  • 构造函数和创建者分离
  • 符合开放封闭原则

# 单例模式(必用)

  • 系统中被唯一使用
  • 一个类只有一个实例

使用场景:登录框、购物车、vuex 和 redux 中的 store、组件的挂载等。

wm-ui 组件的挂载

// 单例模式挂载组件
import Button from './src';
Button.install = function (Vue) {
    if (Button.install.installed) {
        return;
    }
    Vue.component(Button.name, Button);
    Button.install.installed = true;
};
export default Button;
1
2
3
4
5
6
7
8
9
10

设计原则验证

  • 符合单一职责原则,只实例化唯一的对象
  • 没法具体开放封闭原则,但是绝对不违反开放封闭原则

# 适配器模式

  • 旧接口格式和使用者不兼容
  • 中间加一个适配转换接口

使用场景:封装旧接口、vue computed 方法。

wehotel-sdk 的改造

// 自己封装的ajax,使用方式如下
ajax({
    url: '/getData',
    type: 'post',
    dataType: 'json',
    data: {
        id: '123'
    }
}).done(function(){})

// 但因为历史原因,代码中全都是...
$.ajax({...})

// 为了兼容原来的 $.ajax 方法,添加一个适配
var $ = {
    ajax: function(options) {
        return ajax(options);
    }
}
// 旧方法 $.ajax() 和新方法 ajax()可以同时存在使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
    <div>
        <p>normal:{{message}}</p>
        <p>reverse:{{reverseMessage}}</p>
    </div>
</template>
<script>
export default {
    data() {
        return {
            message: 'hello'
        }
    },
    computed: {
        // 倒叙,并且不需要改动message
        reverseMessage() {
            return this.message.split('').reverse().join()
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

设计原则验证

  • 将旧接口与使用者分离
  • 符合开放封闭原则

# 装饰器模式

  • 为对象添加新功能
  • 不改变其原有的结构和功能

使用场景:新功能拓展、ES7 装饰器、工具库core-decorators

ES7 装饰器需要安装插件支持 @babel/plugin-proposal-decorators

class Circle{
    draw() {
        consle.log('画一个圆')
    }
}

class Decorator { 
    constructor(circle) {
        this.circle = circle
    }
    draw() {
        this.circle.draw()
        this.setRedBorder(circle)
    }
    setRedBorder() {
        console.log('设置红色边框')
    }
}

let circle = new Circle()
circle.draw(); // 画一个圆

let dec = new Decorator(circle)
dec.draw(); // 画一个圆  设置红色边框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 装饰器
function testDec(target) {
    target.isDec = true;
}

@testDec
class Demo {
}

console.log(Demo.isDec); // true

1
2
3
4
5
6
7
8
9
10
11

设计原则验证

  • 将享有对象和装饰器进行分离,两者独立存在
  • 符合开放封闭原则

# 代理模式

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

使用场景:网页事件代理、jQuery的 $.proxy 、 ES6 的 Proxy

class ReadImg {
    constructor(filename) {
        this.filename = filename
        this.loadFromDisk()
    }
    display() {
        console.log('display...', this.filename);
    }
    loadFromDisk() {
        console.log('loading...', this.filename);
    }
}

class ProxyImg {
    constructor(filename) {
        this.readImg = new ReadImg(filename)
    }
    display() {
        this.readImg.display()
    }
}

let proxyImg = new ProxyImg('1.png')
// 方法使用等都不变
proxyImg.display()
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
let star = {
    name: 'jacky chen',
    age: 20,
    phone: '10086'
}

let agent = new Proxy(star, {

    get: function(target, key) {
        if (key === 'phone') {
            return 'agent-123456'
        }
        if (key === 'price') {
            return 100000
        }
        return target[key]
    },

    set: function(target, key, val) {
        if (key === 'customPrice') {
            if (val < 100000) {
                throw new Error('价格太低')
            } else {
                target[key] = val
                return true
            }
        }
    }
})
// 通过经纪人获取到明星信息
console.log(agent.name); // jacky chen
console.log(agent.age); // 20
console.log(agent.phone); // agent-123456 经纪人电话
console.log(agent.price); // 100000 经纪人设置报价
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

设计原则验证

  • 代理类和目标类分离,隔离开目标类和使用者
  • 符合开放封闭原则

# 代理模式 VS 适配器模式

  • 适配器模式:提供一个不同的接口(如不同版本的插头)
  • 代理模式:提供一模一样的接口

# 代理模式 VS 装饰器模式

  • 装饰器模式:拓展功能,原有功能不变且可直接使用
  • 代理模式:显示原有功能,但是经过限制或者阉割之后的

# 外观模式

  • 为子系统中的一组接口提供了一个高层接口
  • 使用者使用这个高层接口

使用场景:类似于服务接网关那种形式

// 这种形式应该也是
import * as API from './api-common';

API.getUserInfo()
1
2
3
4

设计原则验证

  • 不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用

# 观察者模式(最常用)

  • 发布 & 订阅
  • 一对多

也就是数据监听,当数据有改变的时候,触发所有的观察者的事件。

使用场景:网页事件绑定、Promise、jQuery callbacks、vue 和 react 组件生命周期触发、vue 的 watch、nodejs 自定义事件、nodejs 中处理http请求,nodejs 的多进程通讯。

class Subject {
    constructor() {
        this.state = 0
        this.observers = []
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
        // 触发所有的观察者
        this.notifyAllObservers()
    }
    notifyAllObservers() {
        this.observers.forEach(observer => {
            observer.update()
        })
    }
    attach(observer) {
        // 新增观察者
        this.observers.push(observer)
    }
}

class Observer {
    constructor(name, subject) {
        this.name = name
        this.subject = subject
        this.subject.attach(this)
    }
    update() {
        console.log(`${this.name} upate, this state ${this.subject.getState()}`);
    }
}

let s1 = new Subject()
let o1 = new Observer('o1', s1)
let o2 = new Observer('o2', s1)
let o3 = new Observer('o3', s1)

s1.setState(1)
s1.setState(2)
s1.setState(3)
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

设计原则验证

  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦
  • 符合开发封闭原则

# 迭代器模式

  • 顺序访问一个集合
  • 使用者无需知道集合的内部结构(封装)

就是在不暴露对象内部结构的同时可以按照一定顺序访问对象内部的元素。

使用场景:jQuery 的 $.each、 ES6 新增的 Generator 函数中的 yield* 、ES6 的 Iterator

// 自定义迭代器
class Iterator {
    constructor(container) {
        this.list = container.list
        this.index = 0
    }
    next() {
        if (this.hasNext()) {
            return this.list[this.index++]
        }
        return null
    }
    hasNext() {
        if (this.index >= this.list.length) {
            return false
        }
        return true
    }
}

class Container {
    constructor(list) {
        this.list = list
    }
    // 生成遍历器
    getInterator() {
        return new Iterator(this)
    }
}

var arr2 = [1, 2, 3, 4]
let container = new Container(arr2)
let iterator = container.getInterator()
while(iterator.hasNext()) {
    console.log(iterator.next()); // 1 2 3 4
}
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

# ES6 Iterator

ES6 的 Iterator 为何存在 ?

ES6 语法中,有序集合的数据类型已经有很多。Array、Map、Set、Argument、NodeList 等(注意,object 不是有序集合)。

所以需要统一的遍历接口来遍历所有的数据类型。

ES6 Iterator 是什么?

Array、Map、Set、Argument、NodeList 等这些数据类型,都有[Symbol.interator]属性。

该属性值是函数,执行函数返回一个迭代器,这个迭代器就有 next 方法可顺序迭代子元素,可运行 Array.prototype[Symbol.interator]来测试。

只要返回的数据符合Iterator的接口要求,就可以使用Iterator语法,这就是迭代器模式

# 状态模式

  • 一个对象有状态变化
  • 每次状态变化都会触发一个逻辑
  • 不能总是用 if...else 来控制

使用场景:有限状态机(如:信号灯的切换)、Promise(promise就是一个有限状态机)

class State {
    constructor(color) {
        this.color = color
    }
    handle(context) {
        console.log(`turn to ${this.color} light`);
        // 把状态挂载
        context.setState(this)
    }
}
// 主体抽象出来
class Context {
    constructor() {
        this.state = null
    }
    // 主体可以获取状态
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
}

let context = new Context()
let red = new State('red')
let yellow = new State('yellow')
let green = new State('green')

red.handle(context) // turn to red light
console.log(context.getState()); // State {color: "red"}

yellow.handle(context) // turn to yellow light
console.log(context.getState()); // State {color: "yellow"}

green.handle(context) // turn to green light
console.log(context.getState()); // State {color: "green"}
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

设计原则验证

  • 将状态对象和主体对象分离,状态的变化逻辑单独处理
  • 符合开放封闭原则
上次更新时间: 3/14/2021, 1:35:34 PM