JS中的常见设计模式及应用场景

能学一点是一点

单例模式(Singleton Pattern)

保证一个类仅有一个实例,并且提供一个访问它的全局访问点。

目的:对于某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在打印;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Singleton {
    constructor(config) {
        this.config = config;
        this.instance = null;
    }
    // 构造一个接口,用于实例化
    static getInstance(config) {
        if(!this.instance) {
            this.instance = new Singleton(config);
        }
        return this.instance;
    }
}
// 调用:
Singleton.getInstance({isSwitchOn: true})

应用场景:

  • 某个配置会被多处使用
  • 全局通知类弹窗,只会出现一个(React v16 Portal)

策略模式(Strategy Pattern)

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

目的:将算法的使用与算法的实现分离开来,使得算法可以被替换和更新,并由调用方决定在何时使用哪种算法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 定义了某种奖金计算算法
const calculateBouns = (salary, level) => {
    if(level === 'A') {
        return salary * 4;
    }else if(level === 'B') {
        return salary * 2;
    }
}
// 调用:
calculateBouns(100, 'A') // 400
calculateBouns(100, 'B') // 200

这种写无数种if else的方式维护性相当不好也不容易扩充和替换新的算法,考虑把每种算法封装成独立方法:

 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
// 封装后的奖金计算策略
class strategyA {
    calculate(salary) {
	    return salary * 4
    }
}
class strategyB {
    calculate(salary) {
	    return salary * 2
    }
}

// 定义奖金类Bonus
class Bonus {
    constructor() {
        this.salary = null;  // 原始工资
	    this.strategy = null;// 等级对应的策略对象
    }
    // 设置原始工资
    setSalary(salary) {
	    this.salary = salary
    }
    // 设置对应的策略对象
    setStrategy(strategy) {
	    this.strategy = strategy
    }
    // 计算奖金
    getBonus() {
	    return this.strategy.calculate(this.salary)
    }
}
// 调用:
const bonus = new Bonus();
bonus.setSalary(100);
bonus.setStrategy(new strategyA());
bonus.getBonus() // 400
bonus.setStrategy(new strategyB());
bonus.getBonus() // 200

应用场景:

  • 消除代码中大片的条件分支语句
  • 表单验证常常需要包含多种不同的匹配算法

代理模式(Proxy Pattern)

给某个对象提供一个代理,并由代理控制对原对象的引用。

目的: 在某些情况下,调用者不想或不能直接调用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接调用。代理对象可以在调用者和目标对象之间起到中介的作用,并且可以通过代理增加或隐藏额外功能。

 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
// 图片预加载: 在真实图片资源尚未加载完成时,先展示loading图,图片下载完成之后再修改img的src属性
// 创建img DOM
const createImg = (function() {
	const node = document.createElement("img")
	document.body.appendChild(node)
	return {
		setSrc: function(src) {
			node.src = src
		}
	}
})()

// 代理对象
const proxy = (function() {
	const img = new Image()
	img.onload = () => createImg.setSrc(img.src)
	return {
		setSrc: function(src) {
			createImg.setSrc("./loading.svg")
			img.src = src
		}
	}
})()

// 调用:
proxy.setSrc("/images/pic.png")

应用场景: 略

装饰模式(Decorator Pattern) (ES7装饰器)

将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为。

目的: 动态地给一个对象增加一些额外的职责,例如增强功能。

1
2
3
4
5
6
7
// 创建秃头码农类
class Programmer{
  // ...
}

// 可以发现并没有头发
Programmer.hair // undefined

我们给他增加一些发量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 生发函数
function breedHair(target) {
  target.hair = 10
}

// 装饰器,duang!
@breedHair
class Programmer{
  // ...
}

// 成了!
Programmer.hair // 10

应用场景:

注解

1
2
3
4
5
@testable
class Person {
	@readonly
	name() { return `${this.first} ${this.last}` }
}

显然,Person类是可测试的,而name方法是只读的。

React 的 connect

使用Redux 时,常常需要写:

1
2
3
4
class Bala extends React.Component {
	// ...
}
export default connect(mapStateToProps, mapDispatchToProps)(Bala);

用装饰器,可以改写为:

1
2
3
4
@connect(mapStateToProps, mapDispatchToProps)
export default class Bala extends React.Component {
	// ...
}

toggleLayer()方法用来向组件state添加layerIndex

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计