[JS]设计模式

介绍

设计模式就是在 面向对象软件 设计过程中, 针对特定问题的简洁而优雅的解决方案

目前说到设计模式, 一般指<设计模式: 可复用面向对象软件的基础>一书中提到的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>

优势

  1. 避免在测试期间, 全局配置污染其它测试用例

  2. 全局改变Vue实例的行为,移到Vue实例上

    <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>

实际应用

单例方法

  • vant中的toast和notify组件

单例思想

  • vue2中的use方法
  • vue3中的use方法

观察者模式

在对象之间定义一个 一对多 的依赖, 当一个对象状态发生改变的时候, 所有依赖的对象都会自动收到通知

发布订阅模式

发布订阅模式在发布者和订阅者之间建立联系, 发布者发布消息, 通过事件总线, 通知所有订阅者

发布订阅vs观察者:

  • 发布订阅模式有中间商, 发布者与订阅者没有直接联系
  • 观察者模式没有中间商, 观察者与目标对象直接联系

应用场景: EventBus

  • vue2: 直接使用实例方法($on, $emit, $off, $once)

  • vue2: 使用第三方插件

    <body>

    发布订阅模式

    <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>
相关推荐
qq_433554541 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
数据小爬虫@20 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.22 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy28 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader35 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell