JS 高级
# 1. 防抖函数
频繁触发事件中,若在 n 秒内事件再次被触发,清除前面的事件,重新计时。
应用:搜索联想、窗口改变等。
const debounce = (fn, wait = 500) => {
let timer
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), wait)
}
}
2
3
4
5
6
7
# 2. 节流函数
频繁触发事件中,n 秒内只会执行一次。
应用:鼠标点击、窗口滚动等。
const throttle = (fn, wait = 500) => {
let inThrottle, timer, lastTime
return function(...args) {
if (!inThrottle) {
fn.apply(this, args)
lastTime = Date.now()
inThrottle = true
} else {
clearTimeout(timer)
timer = setTimeout(() => {
if (Date.now() - lastTime >= wait) {
fn.apply(this, args)
lastTime = Date.now()
}
}, Math.max(wait - (Date.now() - lastTime), 0))
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3. 惰性函数
惰性函数表示函数执行的分支只会在函数第一次调用的时候执行。在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。
应用:浏览器监听事件兼容等。
const time = () => {
const timestamp = Date.now()
time = _ => timestamp
return time()
}
time() // => -28800000
time() // => -28800000
time() // => -28800000
2
3
4
5
6
7
8
9
# 4. 级联函数(链式函数)
级联函数也叫链式函数,方法链一般适合对一个对象进行连续操作。一定程度上可以减少代码量,缺点是它占用了函数的返回值。
jQuery 中运用了大量的链式写法。
function XueDao() {}
function setName(name) {
this.name = name
return this
}
function setAge(age) {
this.age = age
return this
}
function setWife(wife) {
this.wife = wife
return this
}
['setName', 'setAge', 'setWife'].map(key => (XueDao.prototype[key] = eval(key)))
const role = new XueDao()
role
.setName('徐扶墙')
.setAge(17)
.setWife('姜姒') // => {name: '徐扶墙', age: 17, wife: '姜姒'}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5. 偏应用函数
固定一个函数的一些参数,然后产生另一个更小元的函数。偏函数严格来讲是一个减少函数参数个数的过程。
- 理念:降低通用性、增加适用性。
- 作用:参数复用、提前返回、延迟计算/运行。
const partial = (fn, ...presetArgs) => (...laterArgs) =>
fn(...presetArgs, ...laterArgs)
const add = (x, y) => x + y
[770, 1550].map(partial(add, 7)) // => [777, 1557]
2
3
4
5
6
# 6. 柯理化函数
柯里化是偏应用的一种特殊形式。把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果为新函数的技术。将一个多参数(higher-arity)函数拆解为一系列的单元链式函数。
- 理念:降低通用性、增加适用性。
- 作用:参数复用、提前返回、延迟计算/运行。
松散柯里化(一次处理 {0, } 个参数)。
const currying = (fn, arity = fn.length) => (...args) => args.length >= arity ? fn(...args) : currying(fn.bind(null, ...args), arity - args.length) const add = currying((...arg) => eval(arg.join('+')), 3) add()(2)(3, 5) // => 10
1
2
3
4
5
6
7
8
9严格柯里化(一次处理 {1, 1} 个参数)。
const currying = (fn, arity = fn.length, prevArgs = []) => nextArg => { const args = [...prevArgs, nextArg] return args.length >= arity ? fn(...args) : currying(fn, arity, args) } const add = currying((...arg) => eval(arg.join('+')), 3) add(2)(3, 4)(5) // => 10
1
2
3
4
5
6
7
8
9
# 7. 反柯里化函数
反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。目的是创建一个更普适性的函数,可以被不同的对象使用(有鸠占鹊巢的效果)。
增加通用性。
// JavaScript 之父 Brendan Eich
Function.prototype.uncurrying = function() {
var self = this
return function() {
var obj = Array.prototype.shift.call(arguments)
return self.apply(obj, arguments)
}
}
// 另一种实现方式
Function.prototype.uncurrying = function() {
var self = this
return function() {
return Function.prototype.call.apply(self, arguments)
}
}
['shift', 'push', 'forEach'].map(
method => (Array[method] = Array.prototype[method].uncurrying())
)
const foo = {
2: 2,
3: 3,
length: 2,
}
// 方法调用会使用/修改 length 哟!
Array.shift(foo) // foo: {2: 2, 3: 3, length: 1}
Array.push(foo, 5) // foo: {1: 5, 2: 2, 3: 3, length: 2}
// length 为 2,遍历截止到索引为 1
Array.forEach(foo, console.log) // 只打印一次:5 1 {1: 5, 2: 2, 3: 3, length: 2}
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
# 8. 组合函数 (opens new window)
函数组合是指将多个函数按顺序执行,前一个函数的返回值作为下一个函数的参数,最终返回结果。这样做的好处是可以将复杂任务分割成多个子任务,然后通过组合函数再组合成复杂任务。
应用:中间件等。
同步通用组合函数(从右向左执行)。
const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => fn(res), ...args) const add = num => num + 7 compose(add, add)(7) // => 21
1
2
3
4
5
6
7异步通用组合函数(从右向左执行)。
const composePromise = (...fns) => (...args) => fns.reduceRight( (seq, fn) => seq.then(res => fn(res)), Promise.resolve(fns.pop()(...args)) ) const foo = ms => new Promise(resolve => setTimeout(_ => resolve(ms + 1000), ms)) composePromise(foo, foo)(1000).then(console.log) // 约 3 秒后打印 3000
1
2
3
4
5
6
7
8
9
10
11ES 提案了一种管道操作符操作 (opens new window)(从左向右执行)。
const add = num => num + 7 7 >| add >| add // => 21
1
2
3
# 9. 面向切面编程(AOP)
AOP 是针对业务处理过程中的切面进行提取。对重复的非主业务代码进行抽离,降低耦合性,提高代码的复用性,提高开发效率。
应用:错误处理,埋点,日志等。
Function.prototype.before = function(callback) {
if (typeof callback !== 'function')
throw new TypeError('callback is not a function')
const self = this
return function(...args) {
callback.call(this, ...args)
return self.call(this, ...args)
}
}
Function.prototype.after = function(callback) {
if (typeof callback !== 'function')
throw new TypeError('callback is not a function')
const self = this
return function(...args) {
const result = self.call(this, ...args)
callback.call(this, ...args)
return result
}
}
function middle() {
console.log('middle')
return 'result'
}
middle.before(_ => console.log('before')).after(_ => console.log('after'))() // => 依次打印:'before' -> 'middle' -> 'after' -> 'result'
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
# 10. 单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用:登录弹窗等。
const getSingle = fn => {
let result
return function(...args) {
return result || (result = fn.apply(this, args))
}
}
const createModal = function() {
const modal = document.createElement('div')
modal.innerHTML = '登录弹窗'
modal.style.display = 'none'
document.body.appendChild(modal)
return modal
}
const createSingleModal = getSingle(createModal)
createSingleModal() === createSingleModal() // => true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11. 策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
在 JavaScript 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中。我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。
const strategies = {
S: salary => salary * 4,
A: salary => salary * 3,
B: salary => salary * 2,
}
const calculateBonus = (level, salary) => strategies[level](salary)
calculateBonus('S', 777) // => 3108
calculateBonus('A', 777) // => 2331
2
3
4
5
6
7
8
9
10
# 12. 代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
应用:Vue 3.x 版本数据响应式等。
const foo = {
name: '徐扶墙',
age: 17,
wife: ['姜姒', '裴南苇'],
skill: {
name: '天下第二',
},
}
const proxy = new Proxy(foo, {
get(target, key) {
if (key === 'wife') console.log('风紧,扯呼!')
return target[key]
},
set(target, key, value) {
console.log('裆下有些忧郁啊!') // 设置 proxy.skill.name 的值时不会打印这句话
return (target[key] = value)
},
})
proxy.wife // '风紧,扯呼!'
proxy.name = '徐凤年' // '裆下有些忧郁啊!'
proxy.skill.name = '天下第一'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 13. 迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种设计模式。目前的绝大部分语言都内置了迭代器。
['jack', 'pony', 'coderljw'].map(i => console.log(i)) // 依次打印:'jack' -> 'pony' -> 'coderljw'
# 14. 发布订阅模式(观察者模式)
发布订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
应用:DOM2 和 Vue 中的响应式原理、事件监听等。
也可以先发布再订阅。例如微信离线消息,离线消息被保存在服务器中,接收人下次登录上线之后,可以重新收到这条消息。
一部分人认为发布订阅模式与观察者模式是两种模式,认为发布订阅模式是观察者模式的解耦表达。设计模式表达的是程序设计思想,他们区分的发布订阅模式与观察者模式在思想上是一致的。
class Observer {
message = {}
// 监听事件
on(type, cb) {
if (type in this.message) this.message[type].push(cb)
else this.message[type] = [cb]
}
// 触发事件
emit(type, ...args) {
if (type in this.message) this.message[type].forEach(cb => cb(...args))
}
// 取消事件中指定的订阅
off(type, cb) {
if (type in this.message)
this.message[type] = this.message[type].filter(fn => fn !== cb)
}
// 取消事件
offAll(type) {
if (type in this.message) delete this.message[type]
}
}
const event = new Observer()
const handlerClick1 = (...args) => console.log('click1', ...args)
event.on('click', handlerClick1)
event.on('click', (...args) => console.log('click2', ...args))
event.off('click', handlerClick1)
setTimeout(_ => event.emit('click', '徐扶墙', '南宫仆射'), 3000) // 3秒后打印:'click2 徐扶墙 南宫仆射'
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
# 15. 命令模式
命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。
JavaScript 作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了 JavaScript 语言之中。
const bindClick = (button, func) => {
button.onclick = func
}
const MenuBar = {
refresh() {
console.log('刷新菜单界面')
},
}
const SubMenu = {
add() {
console.log('增加子菜单')
},
del() {
console.log('删除子菜单')
},
}
bindClick(button1, MenuBar.refresh)
bindClick(button2, SubMenu.add)
bindClick(button3, SubMenu.del)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 16. 组合模式
组合模式将对象组合成树形结构,以表示 “部分——整体” 的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
应用:文件目录,DOM 文档树。
组合模式应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。
- 命令分发:只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放-封闭原则。
- 统一处理:统一对待树中的所有对象,忽略组合对象和叶对象的区别。
// 文件夹(树对象)
class Folder {
files = []
constructor(name) {
this.name = name
}
add(file) {
this.files.push(file)
}
scan() {
console.log(`开始扫描文件夹:${this.name}`)
this.files.forEach(file => file.scan())
}
}
// 文件(叶对象)
class File {
constructor(name) {
this.name = name
}
add(file) {
throw new Error('文件下面不能再添加文件')
}
scan() {
console.log(`开始扫描文件:${this.name}`)
}
}
const filmTV = new Folder('影视')
const matrixFolder = new Folder('Matrix')
const onePieceFolder = new Folder('ワンピース')
const matrix = new File('The Matrix.mp4')
const onePiece = new File('俺はルフィ!海賊王になる男だ!.mp4')
matrixFolder.add(matrix)
onePieceFolder.add(onePiece)
filmTV.add(matrixFolder)
filmTV.add(onePieceFolder)
filmTV.scan() // 依次打印:'开始扫描文件夹:影视' -> '开始扫描文件夹:Matrix' -> '开始扫描文件:The Matrix.mp4' -> '开始扫描文件夹:ワンピース' -> '开始扫描文件:俺はルフィ!海賊王になる男だ!.mp4'
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
# 17. 模板方法模式
模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
优点:容易扩展和便于维护。
在 JavaScript 中,我们很多时候都不需要依样画瓢地去实现一个模版方法模式,高阶函数是更好的选择。
class Beverage {
boilWater() {
console.log('把水煮沸')
}
// 以下为空方法,应该由子类重写
brew() {}
pourInCup() {}
addCondiments() {}
// 模板方法
init() {
['boilWater', 'brew', 'pourInCup', 'addCondiments'].map(fn => this[fn]())
}
}
class Coffee extends Beverage {
brew() {
console.log('用沸水冲泡咖啡')
}
pourInCup() {
console.log('把咖啡倒进杯子')
}
addCondiments() {
console.log('加糖和牛奶')
}
}
const coffee = new Coffee()
coffee.init() // 依次打印:'把水煮沸' -> '用沸水冲泡咖啡' -> '把咖啡倒进杯子' -> '加糖和牛奶'
class Tea extends Beverage {
brew() {
console.log('用沸水浸泡茶叶')
}
pourInCup() {
console.log('把茶倒进杯子')
}
addCondiments() {
console.log('加柠檬')
}
}
const tea = new Tea()
tea.init() // 依次打印:'把水煮沸' -> '用沸水浸泡茶叶' -> '把茶倒进杯子' -> '加柠檬'
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
# 18. 享元模式
享元(flyweight)模式是一种用于性能优化的模式,“fly” 在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
应用:B 站弹幕等。
对象池是另外一种性能优化方案,它跟享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。
const FILMQUANTITY = 10000,
DIVHEIGHT = 25,
POOLQUANTITY = 20
const films = Array(FILMQUANTITY)
.fill(0)
.map((_, index) => ({
name: `${Math.random()
.toString(36)
.substr(2, 10)} --- ${index}`,
category: Math.random() > 0.5 ? 'SecondDimension' : 'ThirdDimension',
}))
// film 享元对象
class FilmFlyweight {
constructor(category) {
// 定义内部状态
this.category = category
}
// 设置外部状态
setExternalState(state) {
Object.entries(state).forEach(([key, value]) => this[key] = value)
}
getState() {
const { name, category } = this
return { name, category }
}
}
// 创建享元对象(单例模式)
const createFilmFlyweight = (function() {
const store = {}
return function(category) {
if (!store[category]) return (store[category] = new FilmFlyweight(category))
return store[category]
}
})()
// div 享元对象
class Div {
node = document.createElement('div')
// 设置外部状态
setExternalState({ text, serialNumber }, onClick) {
const { node } = this
node.innerText = text
node.style.position = 'absolute'
node.style.top = `${serialNumber * DIVHEIGHT}px`
node.onclick = onClick
}
}
// 创建 div 享元对象
const createDiv = (function() {
// 对象池
const pool = []
return function(container) {
let div
if (pool.length <= POOLQUANTITY) div = new Div()
else div = pool.shift()
pool.push(div)
container.appendChild(div.node)
return div
}
})()
// 创建容器
const { outerContainer, innerContainer } = (function() {
const outerContainer = document.createElement('div')
const innerContainer = document.createElement('div')
outerStyle = {
width: '200px',
height: '400px',
maxHeight: '400px',
border: '1px solid red',
overflow: 'auto',
}
innerStyle = {
position: 'relative',
height: `${DIVHEIGHT * films.length}px`,
}
Object.entries(outerStyle).forEach(([key, value]) => outerContainer.style[key] = value)
Object.entries(innerStyle).forEach(([key, value]) => innerContainer.style[key] = value)
outerContainer.appendChild(innerContainer)
document.body.appendChild(outerContainer)
return { outerContainer, innerContainer }
})()
// 加载节点
function loadNode(start, end) {
films.slice(start, end).forEach((filmData, index) => {
const filmFlyweight = createFilmFlyweight(filmData.category)
const div = createDiv(innerContainer)
div.setExternalState(
{ text: filmData.name, serialNumber: start + index },
() => {
filmFlyweight.setExternalState({ name: filmData.name })
console.log(filmFlyweight.getState())
}
)
})
}
loadNode(0, POOLQUANTITY)
let current = 0
outerContainer.addEventListener('scroll', e => {
const start = (outerContainer.scrollTop / DIVHEIGHT) | 0
if (start !== current) {
loadNode(start, start + POOLQUANTITY)
current = start
}
})
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
# 19. 职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
应用:拦截器等。
const order500 = (type, pay, stock) => {
if (type === 1 && pay) return '500 元定金预购,得到 100 优惠券'
return 'nextSuccessor'
}
const order200 = (type, pay, stock) => {
if (type === 2 && pay) return '200 元定金预购,得到 50 优惠券'
return 'nextSuccessor'
}
const orderNormal = (type, pay, stock) => {
if (stock > 0) return '普通购买,无优惠券'
return '手机库存不足'
}
Function.prototype.after = function(callback) {
if (typeof callback !== 'function')
throw new TypeError('callback is not a function')
const self = this
return function(...args) {
const result = self.call(this, ...args)
if (result === 'nextSuccessor') return callback.call(this, ...args)
return result
}
}
const order = order500.after(order200).after(orderNormal)
order(1, true, 500) // => '500 元定金预购,得到 100 优惠券'
order(2, true, 500) // => '200 元定金预购,得到 50 优惠券'
order(1, false, 500) // => '普通购买,无优惠券'
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
# 20. 中介者模式
中介者模式的作用就是解除对象与对象之间的紧耦合关系,使网状的多对多关系变成了相对简单的一对多关系。
中介者模式和发布订阅模式非常相似,区别在于通信方式和结构上的差异。
- 中介者模式中每个模块都可以发布消息(中介者本身也可以发布消息),而发布订阅模式中观察者只能被动的等待消息。
- 中介者模式是星状结构,中介者是一个 ”控制点“,而发布订阅机制本身是一个 ”控制层”,意味着高层可以通过控制层操作下层模块(当然也可以通过代码实现下层控制高层)。
class Player {
partners = [] // 队友列表
enemies = [] // 敌人列表
state = 'live' // 玩家状态
constructor(name, teamColor) {
this.name = name // 角色名字
this.teamColor = teamColor // 队伍颜色
}
// 胜利
win() {
console.log('winner: ' + this.name)
}
// 失败
lose() {
console.log('loser: ' + this.name)
}
// 死亡
die() {
const { partners, enemies } = this
this.state = 'dead'
const allDead = partners.every(i => i.state === 'dead')
if (allDead === true) {
this.lose()
partners.map(i => i.lose())
enemies.map(i => i.win())
}
}
}
// 玩家列表
const players = []
// 创建玩家
const createPlayer = (name, teamColor) => {
const newPlayer = new Player(name, teamColor)
players.map(i => {
if (i.teamColor === newPlayer.teamColor) {
// 同队
i.partners.push(newPlayer)
newPlayer.partners.push(i)
} else {
// 敌队
i.enemies.push(newPlayer)
newPlayer.enemies.push(i)
}
})
players.push(newPlayer)
return newPlayer
}
// 红队
const player1 = createPlayer('Neo', 'red'),
player2 = createPlayer('Trinity', 'red'),
player3 = createPlayer('Morpheus', 'red'),
player4 = createPlayer('Tank', 'red')
// 蓝队
const player5 = createPlayer('Smith', 'blue'),
player6 = createPlayer('Oracle', 'blue'),
player7 = createPlayer('Architect', 'blue'),
player8 = createPlayer('Merovingian', 'blue')
player5.die()
player6.die()
player7.die()
player8.die()
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
# 21. 装饰者模式
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种 “即用即付” 的方式。
const hero = { buff() { console.log('蓝BUFF') }, } const redDecorator = () => { console.log('红BUFF') } const dragonDecorator = () => { console.log('大龙BUFF') } const buff1 = hero.buff hero.buff = () => { buff1() redDecorator() } const buff2 = hero.buff hero.buff = () => { buff2() dragonDecorator() } hero.buff() // => 依次打印:蓝BUFF -> 红BUFF -> 大龙BUFF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22ES 提案中的装饰器(ECMAScript 6 入门 - 阮一峰 (opens new window))。
const blueDecorator = () => { console.log('蓝BUFF') } const redDecorator = () => { console.log('红BUFF') } const dragonDecorator = () => { console.log('大龙BUFF') } class Hero { @blueDecorator @redDecorator @dragonDecorator buff() {} }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 22. 状态模式
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
应用:React、Vue 等框架中的组件。
与策略模式之间的区别:策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法。而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为” 这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节,这正是状态模式的作用所在。
const delegate = (client, delegation) => {
return {
buttonWasPressed() {
// 将客户的操作委托给 delegation 对象
return delegation.buttonWasPressed.apply(client, arguments)
},
}
}
const FSM = {
off: {
buttonWasPressed() {
console.log('关灯')
this.button.innerHTML = '下一次按我是开灯'
this.currentState = this.onState
},
},
on: {
buttonWasPressed() {
console.log('开灯')
this.button.innerHTML = '下一次按我是关灯'
this.currentState = this.offState
},
},
}
class Light {
button
constructor() {
this.offState = delegate(this, FSM.off)
this.onState = delegate(this, FSM.on)
this.currentState = this.offState // 设置初始状态为关闭状态
}
init() {
const button = document.createElement('button')
button.innerHTML = '已关灯'
this.button = document.body.appendChild(button)
this.button.onclick = _ => this.currentState.buttonWasPressed()
}
}
const light = new Light()
light.init()
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
# 23. 适配器模式
适配器模式是一对相对简单的模式。作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
应用:Vue 中的 computed 等。
如果现有的接口已经能够正常工作,那我们就永远不会用上适配器模式。适配器模式是一种 “亡羊补牢” 的模式,没有人会在程序的设计之初就使用它。
const googleMap = {
show() {
console.log('开始渲染谷歌地图')
},
}
const baiduMap = {
display() {
console.log('开始渲染百度地图')
},
}
// 适配 renderMap
const baiduMapAdapter = {
show() {
return baiduMap.display()
},
}
const renderMap = map => map?.show?.()
renderMap(googleMap)
renderMap(baiduMapAdapter)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23