JavaScript 设计模式原则

569 阅读3分钟

单一职责原则

一个方法负责一项职责,只能有一项引起函数变化的原因

当一个函数用来处理多个事件时,我们需要对其进行分离,便于增加函数的可读性,方便对函数进行扩充。例如:当我们异步加载图片时

function loadImg() ({
    let img = new Image()
    document.body.append(img)
    return function(imgNode) {
    	img.src = imgNode
    }
})()

function ProxyImg() (function() {
	let img = new Image()
	img.onload = function() {
		loadImg(this.src)
	}
	return function(imgNode) {
		img.src = imgNode
		loadImg('占位图片')
	}
})()
proxyImg('https://ww.example.com/1.jpg')

通过单一职责原则,我们把异步加载图片和设置图片的职能分开,就算以后不用异步加载图片了,直接把 proxyImg 换成 loadImg 就行了,不用再更改函数

单一职责原则也有一些缺点,最明显的就是增加了代码复杂度,也增加了对象之间的联系难度

最少知识原则

一个对象应该对其他对象保持最少的了解

最少知识原则要求我们尽量减少两个对象之间的交互,这时候可以引入一个”第三者“,

比如说中介者模式,当多个对象需要交互时,我们引入一个中介者,用来处理逻辑,防止各个对象之间进行交互。例如:当我们进行多人对战游戏时,当剩余一半玩家时,则剩余玩家获得胜利:

当用户死亡时,如直接交互,可能需要向其余对象各发布一次死亡信息,其他对象需要自身对其他用户状况进行保存,这样进一步增加了代码的复杂度
当我们引入一个中介者来处理事件逻辑时:

class User {
	constructor(name) {
		this.name = name
		this.state = 'alive'
	}
	die() {
		this.state = 'dead'
		userAgent.lose()
	}
	win() {
		console.log(this.name + '胜利')
	}
	lose() {
		console.log(this.name + '失败')
	}
}
function userFactory(name) {
	let user = new User(name)
  userAgent.add(user)
  return user
}

// 中介,用来处理中间逻辑
userAgent = (function() {
	let players = []
	function add(user) {
		players.push(user)
	}
	function lose() {
		let deadNum = 0
		for (let i = 0; i < players.length; i++) {
			if (players[i].state === 'dead') {
				deadNum += 1
			}
		}
		if (deadNum >= Math.round(players.length / 2)) {
			players.forEach(user => {
				if (user.state === 'dead') {
					user.lose()			
				}
				else {
					user.win()
				}
			})
		}
	}
	return {
		add,
		lose
	}
})()

let user1 = userFactory('111')
let user2 = userFactory('222')
let user3 = userFactory('333')
let user4 = userFactory('444')
user3.die()
user2.die()

// 111胜利
// 222失败
// 333失败
// 444胜利

遵守最少知识原则可以减少不必要的交互,降低各个函数之间的耦合性

但是遵守最少知识原则缺点也很明显:会增加代码的复杂度。在示例中,我们为了避免直接交互,引入了一个”中介“,导致代码复杂度增加,因此,使用时应该权衡利弊。

开放闭合原则

当需要改变程序功能时,可以通过对其进行扩展,但是不允许改变原函数

当我们为旧代码中的函数添加新功能时,直接更改函数往往会引起不必要的错误,开放闭合原则让我们不用更改源码,而是在函数上进行二次扩展,常用形式比如说函数 AOP 插槽,通过放置 hook 来进行扩展

例如:常用的对函数原型链进行 before after 扩展

Function.prototype.before = function(fn) {
  let self = this
  return function() {
    fn.apply(this, arguments)
    return self.apply(this, arguments)
  }
}
Function.prototype.after = function(fn) {
  let self = this
  return function() {
    let ret = self.apply(this, arguments)
    fn.apply(this, arguments)
    return ret
  }
}

function test() {
  console.log('test')
}

var test = test
	.before(function() {
	  console.log('before')
	})
	.after(function() {
	  console.log('after')
	})

test()
// before
// test
// after

扩展的函数不会改变原函数的执行结果,还可以共享 arguments

基本上 JavaScript 中常用的设计模式都遵守 开放-封闭原则