介绍
设计模式就是在 面向对象软件 设计过程中, 针对特定问题的简洁而优雅的解决方案
目前说到设计模式, 一般指<设计模式: 可复用面向对象软件的基础>一书中提到的23种常见软件设计模式
工厂模式
在JavaScript中, 工厂模式的表现形式就是一个 调用即可返回新对象 的函数
<script>// 1.普通函数function FoodFactory(name, color) {return {name,color}}const f1 = FoodFactory('apple', 'red')// 2.构造函数function Food(name, color) {this.name = namethis.color = color}const f2 = new Food('apple', 'red')</script>
<script>// 1.vue3 - createAppimport { createApp } from 'vue'const app = createApp({})</script>
优势
- 避免在测试期间, 全局配置污染其它测试用例
- 全局改变Vue实例的行为,移到Vue实例上
<script>// 2. axios-createimport 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>
实际应用
单例方法
- vant中的toast和notify组件
单例思想
- vue2中的use方法
- vue3中的use方法
观察者模式
在对象之间定义一个 一对多 的依赖, 当一个对象状态发生改变的时候, 所有依赖的对象都会自动收到通知
发布订阅模式
发布订阅模式在发布者和订阅者之间建立联系, 发布者发布消息, 通过事件总线, 通知所有订阅者
发布订阅vs观察者:
- 发布订阅模式有中间商, 发布者与订阅者没有直接联系
- 观察者模式没有中间商, 观察者与目标对象直接联系
应用场景: EventBus
- vue2: 直接使用实例方法($on, $emit, $off, $once)
- vue2: 使用第三方插件
<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 = 0const 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>