[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>
相关推荐
^^为欢几何^^7 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
Hello-Mr.Wang12 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
救救孩子把15 分钟前
Java基础之IO流
java·开发语言
WG_1716 分钟前
C++多态
开发语言·c++·面试
宇卿.22 分钟前
Java键盘输入语句
java·开发语言
Amo Xiang32 分钟前
2024 Python3.10 系统入门+进阶(十五):文件及目录操作
开发语言·python
程序员凡尘39 分钟前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
friklogff1 小时前
【C#生态园】提升C#开发效率:深入了解自然语言处理库与工具
开发语言·c#·区块链
仙魁XAN1 小时前
Unity 设计模式 之 创造型模式-【工厂方法模式】【抽象工厂模式】
unity·设计模式·工厂方法模式·抽象工厂模式
重生之我在20年代敲代码2 小时前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记