设计模式
工厂模式
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题题(即新创建的对象是什么类型)
构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
在这个例子中,Person()
构造函数代替了 createPerson()
工厂函数。实际上,Person()
内部的代码跟 createPerson()
基本是一样的,只是有如下区别。
没有显式地创建对象。
属性和方法直接赋值给了 this。
没有 return
要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作。
(1) 在内存中创建一个新对象。
(2) 这个新对象内部的[[Prototype]]
特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
(4) 执行构造函数内部的代码(给新对象添加属性)。
(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
构造函数也是函数, 与普通函数唯一的区别就是调用方式不同
构造函数的问题
构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍. 即每次new都会创造自己的Function
但是, 如果把函数定义在构造函数外, 样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数. 这个新问题可以通过原型模式来解决。
原型模式
每个函数都会创建一个 prototype
属性,这个属性是一个对象,包含应该由特定引用类型 (函数/对象) 的实例共享的属性和方法. 这个对象就是通过调用构造函数创建的对象的原型
// 所以, 就是把要共享的 函数\属性 挂载到公共的
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // "Nicholas"
let person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true
与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。
适配器模式
接口不兼容,即使用的属性不一样
比如a组件展示name
, tilte
,接口回来的是PigName
, PigTime
就要用适配器做一层转换;
又比如说不同的库暴露出来的方法不一致,这时候就要用适配器保证调用的接口一致
class GooleMap {
show() {
console.log('渲染谷歌地图')
}
}
class BaiduMap {
display() {
console.log('渲染百度地图')
}
}
// 定义适配器类, 对BaiduMap类进行封装
class BaiduMapAdapter {
show() {
var baiduMap = new BaiduMap()
return baiduMap.display()
}
}
function render(map) {
if (map.show instanceof Function) {
map.show()
}
}
render(new GooleMap()) // 渲染谷歌地图
render(new BaiduMapAdapter()) // 渲染百度地图
适配器模式符合开放封闭原则
关于开放封闭原则,其核心的思想是:
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
因此,开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
“需求总是变化”、“世界上没有一个软件是不变的”,这些言论是对软件需求最经典的表白。从中透射出一个关键的意思就是,对于软件设计者来说,必须在不需要对原有的系统进行修改的情况下,实现灵活的系统扩展。而如何能做到这一点呢?
代理模式
vue3 proxy
- 代理模式符合开放封闭原则
- 本体对象和代理对象拥有相同的方法,在用户看来并不知道请求的本体对象还是代理对象。
迭代器模式
es6迭代器
策略模式
定义一系列的算法,把它们一个个封装起来,并使它们可以替换
var fnA = function(val) {
return val * 1
}
var fnB = function(val) {
return val * 2
}
var fnC = function (val) {
return val * 3
}
var calculate = function(fn, val) {
return fn(val)
}
console.log(calculate(fnA, 100))// 100
console.log(calculate(fnB, 100))// 200
console.log(calculate(fnC, 100))// 300
组合模式
- 组合模式在对象间形成
树形结构
;- 组合模式中基本对象和组合对象被
一致对待
;- 无须关心对象有多少层, 调用时只需在根部进行调用;
const MacroCommand = function() {
return {
lists: [],
add: function(task) {
this.lists.push(task)
},
excute: function() { // ①: 组合对象调用这里的 excute,
for (let i = 0; i < this.lists.length; i++) {
this.lists[i].excute()
}
},
}
}
const command1 = MacroCommand() // 基本对象
command1.add({
excute: () => console.log('煮咖啡') // ②: 基本对象调用这里的 excute,
})
const command2 = MacroCommand() // 组合对象
command2.add({
excute: () => console.log('打开电视')
})
command2.add({
excute: () => console.log('打开音响')
})
const command3 = MacroCommand()
command3.add({
excute: () => console.log('打开空调')
})
command3.add({
excute: () => console.log('打开电脑')
})
const macroCommand = MacroCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.add(command3)
macroCommand.excute()
// 煮咖啡
// 打开电视
// 打开音响
// 打开空调
// 打开电脑
单例模式
- 单例模式的主要思想就是,实例如果已经创建,则直接返回
- 符合开放封闭原则
观察者模式(发布-订阅模式)
class Event {
constructor() {
this.eventTypeObj = {}
}
on(eventType, fn) {
if (!this.eventTypeObj[eventType]) {
// 按照不同的订阅事件类型,存储不同的订阅回调
this.eventTypeObj[eventType] = []
}
this.eventTypeObj[eventType].push(fn)
}
emit() {
// 可以理解为arguments借用shift方法
var eventType = Array.prototype.shift.call(arguments)
var eventList = this.eventTypeObj[eventType]
for (var i = 0; i < eventList.length; i++) {
eventList[i].apply(eventList[i], arguments)
}
}
remove(eventType, fn) {
// 如果使用remove方法,fn为函数名称,不能是匿名函数
var eventTypeList = this.eventTypeObj[eventType]
if (!eventTypeList) {
// 如果没有被人订阅改事件,直接返回
return false
}
if (!fn) {
// 如果没有传入取消订阅的回调函数,则改订阅类型的事件全部取消
eventTypeList && (eventTypeList.length = 0)
} else {
for (var i = 0; i < eventTypeList.length; i++) {
if (eventTypeList[i] === fn) {
eventTypeList.splice(i, 1)
// 删除之后,i--保证下轮循环不会漏掉没有被遍历到的函数名
i--;
}
}
}
}
}
var handleFn = function(data) {
console.log(data)
}
var event = new Event()
event.on('click', handleFn)
event.emit('click', '1') // 1
event.remove('click', handleFn)
event.emit('click', '2') // 不打印
- 发布订阅模式可以使代码解耦,满足开放封闭原则
- 当过多的使用发布订阅模式,如果订阅消息始终都没有触发,则订阅者一直保存在内存中。
命令模式
vuex算吗
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cmd-demo</title>
</head>
<body>
<div>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<!-- <button id="btn3">按钮3</button>-->
</div>
<script>
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
// var btn3 = document.getElementById('btn3')
// 定义一个命令发布者(执行者)的类
class Executor {
setCommand(btn, command) {
btn.onclick = function() {
command.execute()
}
}
}
// 定义一个命令接收者
class Menu {
refresh() {
console.log('刷新菜单')
}
addSubMenu() {
console.log('增加子菜单')
}
}
// 定义一个刷新菜单的命令对象的类
class RefreshMenu {
constructor(receiver) {
// 命令对象与接收者关联
this.receiver = receiver
}
// 暴露出统一的接口给命令发布者Executor
execute() {
this.receiver.refresh()
}
}
// 定义一个增加子菜单的命令对象的类
class AddSubMenu {
constructor(receiver) {
// 命令对象与接收者关联
this.receiver = receiver
}
// 暴露出统一的接口给命令发布者Executor
execute() {
this.receiver.addSubMenu()
}
}
var menu = new Menu()
var executor = new Executor()
var refreshMenu = new RefreshMenu(menu)
// 给按钮1添加刷新功能
executor.setCommand(btn1, refreshMenu)
var addSubMenu = new AddSubMenu(menu)
// 给按钮2添加增加子菜单功能
executor.setCommand(btn2, addSubMenu)
// 如果想给按钮3增加删除菜单的功能,就继续增加删除菜单的命令对象和接收者的具体删除方法,而不必修改命令对象
</script>
</body>
</html>
- 命令接收者 receiver:实现命令的类
- 命令发布者(执行者)Executor:绑定元素并触发执行执行函数的类
- 功能类,暴露exectue方法,接收receiver引用
状态模式
举一个关于开关控制电灯的例子,电灯只有一个开关
- 第一次按下打开弱光
- 第二次按下打开强光
- 第三次按下关闭。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>state-demo</title>
</head>
<body>
<button id="btn">开关</button>
<script>
// 定义一个关闭状态的类
class OffLightState {
constructor(light) {
this.light = light
}
// 每个类都需要这个方法,在不同状态下按都需要触发这个方法
pressBtn() {
this.light.setState(this.light.weekLightState)
console.log('开启弱光')
}
}
// 定义一个弱光状态的类
class WeekLightState {
constructor(light) {
this.light = light
}
pressBtn() {
this.light.setState(this.light.strongLightState)
console.log('开启强光')
}
}
// 定义一个强光状态的类
class StrongLightState {
constructor(light) {
this.light = light
}
pressBtn() {
this.light.setState(this.light.offLightState)
console.log('关闭电灯')
}
}
class Light {
constructor() {
this.offLightState = new OffLightState(this)
this.weekLightState = new WeekLightState(this)
this.strongLightState = new StrongLightState(this)
this.currentState = null
}
setState(newState) {
this.currentState = newState
}
init() {
this.currentState = this.offLightState
}
}
let light = new Light()
light.init()
var btn = document.getElementById('btn')
btn.onclick = function() {
light.currentState.pressBtn()
}
</script>
</body>
</html>
给三个状态都初始化一个类,各自持有pressBtn方法,点击就触发公共的setState,传入灯的状态,触发状态改变
职责链模式
外观(facade)模式
我的理解是把一组功能抽象成一个函数,有统一的出入口,