设计模式就是在 面向对象软件 设计过程中, 针对特定问题的简洁而优雅的解决方案
目前说到设计模式, 一般指<设计模式: 可复用面向对象软件的基础>一书中提到的23种常见软件设计模式
在JavaScript中, 工厂模式的表现形式就是一个 调用即可返回新对象 的函数
<script>
// 1.普通函数
function FoodFactory(name, color) {
return {
name,
color
}
}
const f1 = FoodFactory('apple', 'red')
// 2.构造函数
function Food(name, color) {
this.name = name
this.color = color
}
const f2 = new Food('apple', 'red')
</script>
<script>
// 1.vue3 - createApp
import { createApp } from 'vue'
const app = createApp({})
</script>
优势
<script>
// 2. axios-create
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:3000',
timeout: 1000
})
</script>
单例模式就是保证整个系统只有一个对象存在
<script>
// 实例单例
// 1.定义类
class SingleTon {
// 2.定义私有属性, 用于保存实例对象
static #instance
// 3.定义静态方法, 用于创建实例对象
static getInstance() {
// 4.判断
if (this.#instance === undefined) {
this.#instance = new SingleTon()
}
// 5.返回对象
return this.#instance
}
}
// 测试代码
const s1 = SingleTon.getInstance()
const s2 = SingleTon.getInstance()
console.log(s1 === s2) // ture
</script>
实际应用
单例方法
单例思想
在对象之间定义一个 一对多 的依赖, 当一个对象状态发生改变的时候, 所有依赖的对象都会自动收到通知
发布订阅模式在发布者和订阅者之间建立联系, 发布者发布消息, 通过事件总线, 通知所有订阅者
发布订阅vs观察者:
应用场景: EventBus
<body>
<h2>发布订阅模式</h2>
<button class="on">注册事件</button>
<button class="emit">触发事件</button>
<button class="off">移除事件</button>
<button class="once-on">一次性事件注册</button>
<button class="once-emit">一次性事件触发</button>
<script>
class EventEmitter {
#handlers = {
//事件名: [callback1, callback2]
}
/**
* 注册事件监听
* 1.1添加私有属性
* 1.2保存事件
*/
$on(event, callback) {
if (!this.#handlers[event]) {
this.#handlers[event] = []
}
this.#handlers[event].push(callback)
}
/**
* 触发事件
* 2.1接受不定长参数
* 2.2循环触发事件
*/
$emit(event, ...args) {
const funcs = this.#handlers[event] || []
funcs.forEach(callback => {
callback(...args)
});
}
/**
* 移除事件
* 3.1清空事件
*/
$off(event) {
this.#handlers[event] = undefined
}
/**
* 一次性事件
* 4.1调用$on注册事件
* 4.2事件内调用$off移除事件
*/
$once(event, callback) {
this.$on(event, (...args) => {
callback(...args)
this.$off(event)
})
}
}
// 测试代码
const event = new EventEmitter()
// 注册事件
document.querySelector('.on').addEventListener('click', function () {
event.$on('event1', () => { console.log('1111'); })
event.$on('event1', () => { console.log('2222'); })
event.$on('event2', () => { console.log('3333'); })
})
// 触发事件
document.querySelector('.emit').addEventListener('click', function () {
event.$emit('event1')
event.$emit('event2')
})
// 移除事件
document.querySelector('.off').addEventListener('click', function () {
event.$off('event1')
})
// 一次性事件注册
document.querySelector('.once-on').addEventListener('click', function () {
event.$once('event3', () => { console.log(4444); })
})
// 一次性事件触发
document.querySelector('.once-emit').addEventListener('click', function () {
event.$emit('event3')
})
</script>
</body>
原型模式是创建型模式的一种, 其特点在于通过 复制 一个已经存在的实例来返回新的实例, 而不是新建实例
<script>
const food = {
name: '西蓝花',
eat() {
console.log('好好吃');
}
}
// 复制新对象
// Object.create 将对象作为原型, 创建新对象
const f = Object.create(food);
console.log(f === food); // fasel
</script>
应用: Vue2源码中, 对于数组变更方法的封装, 使用到了该模式
代理模式是为一个对象提供一个代理, 以便控制对它的访问
缓存代理
<body>
<button id="btn">发起请求</button>
<script>
/**
* 缓存代理
* 需求: 第一次查询的数据通过接口获取, 重复查询通过缓存获取
*/
// 1创建对象缓存数据
const cache = {}
document.querySelector('#btn').addEventListener('click', async () => {
// 2判断是否缓存数据
console.log(cache);
if (!cache.pname) {
const search = new URLSearchParams({ pname: '广东省' }).toString()
const res = await fetch('http://hmajax.itheima.net/api/city?' + search)
const data = await res.json()
// 3缓存数据
cache.pname = data.list
}
// 4返回数据
return cache.pname
})
</script>
</body>
可以让用户透过特定的接口巡防容器中的每一个元素而不用了解底层的实现(遍历)
<script>
Object.prototype.objFunc = function () { }
Array.prototype.arrFunc = function () { }
const foods = ['西蓝花', '菜花', '西葫芦']
/**
* for in
* 遍历一个对象(除了Symbol)的属性(可枚举), 包括继承的可枚举属性
* 遍历的出是索引
* 继承来的也可以遍历出来(原型链上动态增加的也可以遍历)
*/
for (let key in foods) {
console.log('key', key) //0,1,2,objFunc,arrFunc
}
</script>
<script>
Object.prototype.objFunc = function () { }
Array.prototype.arrFunc = function () { }
const foods = ['西蓝花', '菜花', '西葫芦']
/**
* for of
* for of可以遍历可迭代对象
* 包括 Array Map Set String TypeArray Argumente ... ...
* 无法遍历 Object
* 遍历的是值
* 继承来的不会遍历出来
*/
for (let food of foods) {
console.log('food', food) //西蓝花,菜花,西葫芦
}
</script>
迭代协议
迭代协议可以制定对象的迭代行为, 遵循协议的对象都是可迭代对象, 迭代器协议分为 可迭代协议 和 迭代器协议, 只要对象循序这两个协议之一, 就可以被for of遍历
<script>
// 可迭代协议:
// 增加方法[Symbol.iterator](){}
// 返回可迭代对象
const obj = {
// Symbol.inerator 内置的常量
// [属性名表达式]
[Symbol.iterator]() {
// 使用Generator实现可迭代协议
function* foodGenerator() {
yield '西蓝花'
yield '菜花'
yield '西葫芦'
}
const food = foodGenerator()
return food
}
}
for (const iterator of obj) {
console.log('iterator', iterator); // 西蓝花,菜花,西葫芦
}
</script>
<script>
// 迭代器协议:
// 有 next方法的对象, next方法返回:
// 已结束: {done: true}
// 继续迭代: {done: false, value: 'x'}
const obj = {
// 自己编写next方法实现迭代器协议
[Symbol.iterator]() {
let index = 0
const arr = ['西蓝花', '菜花', '西葫芦']
return {
next() {
if (index < arr.length) {
// 继续迭代
return { done: false, value: arr[index++] }
} else {
// 迭代完毕
return { done: true }
}
}
}
}
}
for (const iterator of obj) {
console.log('iterator', iterator); //西蓝花,菜花,西葫芦
}
</script>