输入搜索…

· client · 12 min 阅读

常见的 JS 设计模式-上

学习常见的 11 种 JS 设计模式

学习常见的 11 种 JS 设计模式

@iriser

11 种常见的 JS 设计模式-上

  1. 单例模式(Singleton Pattern)
  2. 工厂模式(Factory Pattern)
  3. 构造函数模式(Constructor Pattern)
  4. 模块模式(Module Pattern)
  5. 观察者模式(Observer Pattern)
  6. 代理模式(Proxy Pattern)
  7. 装饰器模式(Decorator Pattern)
  8. 适配器模式(Adapter Pattern)
  9. 命令模式(Command Pattern)
  10. 迭代器模式(Iterator Pattern)
  11. 发布-订阅模式(Publish-Subscribe Pattern)

1. 单例模式

用于确保一个类只有一个实例,并提供一个全局访问点。这在许多情况下都是有用的,例如全局状态管理、数据库连接、日志记录器等。

优点:

  1. 节省内存: 单例模式确保只有一个实例存在,这可以节省内存空间。
  2. 全局访问点: 可以通过单例模式提供的全局访问点来访问实例,简化了对象的访问。
  3. 避免重复实例化: 单例模式确保只有一个实例存在,避免了重复实例化带来的性能损耗。

缺点:

  1. 全局状态: 单例模式会引入全局状态,可能导致代码的耦合度增加,使得代码难以维护和测试。
  2. 隐藏依赖: 全局访问点可能会隐藏依赖关系,使得代码更难理解和测试。
  3. 并发风险: 如果单例模式没有考虑并发访问的情况,可能会引发并发风险,需要额外的同步机制来解决。

实现方式:

  1. 使用字面量方式创建单例:

        const signleton = {
      property1: 'value1',
      property2: 'value2',
      method: function () {
        // do something
      }
    }
    
  2. 使用函数闭包创建单例:

        const Singleton = (function () {
      let instance
      function init() {
        // private methods and variables
        return {
          // public methods and variables
        }
      }
      return {
        getInstance: function () {
          if (!instance) {
            instance = init()
          }
          return instance
        }
      }
    })()
    
  3. 使用类创建单例:

        class Singleton {
      constructor() {
        if (!Singleton.instance) {
          // initialization code
          Singleton.instance = this
        }
        return Singleton.instance
      }
      // methods
    }
    // 使用
    const singletonInstance = new Singleton()
    

2. 工厂模式

通过定义一个公告的接口来创建对象,但是具体创建哪种类型的对象则由工厂方法来决定。工厂模式隐藏了对象的创建逻辑,使得客户端代码不需要直接实例化对象,而是通过调用工厂的方法来获取所需的对象实例。

优点:

  1. 封装性高: 工厂模式封装了对象的创建过程,使得客户端不需要知道具体的创建细节,降低了代码的耦合度。
  2. 灵活性: 工厂模式可以根据需要动态创建不同类型的对象,从而实现灵活的对象创建。
  3. 可维护性: 由于对象的创建逻辑集中在工厂方法中,修改创建逻辑只需要修改工厂方法,不会影响客户端代码,提高了代码的可维护性。
  4. 利于拓展: 可以随时增加新的对象类型,而不需要修改客户端代码。

缺点:

  1. 增加复杂度: 引入工厂模式会增加代码的复杂度,因为需要额外定义工厂方法来创建对象。
  2. 难以追踪对象来源: 使用工厂模式创建的对象,很难追踪对象的具体来源,因为对象的创建过程被封装在工厂方法内部。

实现方式:

  1. 简单工厂模式:

        function createCar(type) {
      if (type === 'SUV') {
        return new SUV()
      } else if (type === 'Sedan') {
        return new Sedan()
      } else if (type === 'Hatchback') {
        return new Hatchback()
      } else {
        throw new Error('Invalid car type')
      }
    }
    
    const myCar = createCar('SUV')
    
  2. 工厂方法模式:

        class CarFactory {
      createCar(type) {
        if (type === 'SUV') {
          return new SUV()
        } else if (type === 'Sedan') {
          return new Sedan()
        } else if (type === 'Hatchback') {
          return new Hatchback()
        } else {
          throw new Error('Invalid cat type')
        }
      }
    }
    
    // 使用
    const factory = new CarFactory()
    const myCar = factory.createCar('SUV')
    
  3. 抽象工厂模式:

        class CarFactory {
      createSUV() {
        return new SUV()
      }
      createSedan() {
        return new Sedan()
      }
      createHatchback() {
        return new Hatchback()
      }
    }
    
    // 使用
    const factory = new CarFactory()
    const mySUV = factory.createSUV()
    const mySedan = factory.createSedan()
    const myHatchback = factory.createHatchback()
    

3. 构造函数模式

一种用于创建对象的方式,它使用构造函数来定义对象的属性和方法,并通过 new 关键字来创建对象实例。

优点:

  1. 简单易懂: 构造函数模式是一种简单直观的创建对象方式,易于理解和使用。
  2. 支持继承: 可以通过在构造函数的原型对象上定义方法,实现对象之间的继承关系。
  3. 封装性: 可以利用构造函数内部的局部变量和方法实现对象的封装,保护数据安全性。

缺点:

  1. 每个实例都会创建新的方法: 每次使用构造函数创建对象实例时,都会重新创建一组新的方法,可能会占用较多内存。
  2. 无法复用方法: 每个实例都会拥有一组新的方法,无法实现方法的复用,可能会导致内存浪费。

实现方式:

    // 构造函数模式的实现
function Person(name, age) {
  this.name = name
  this.age = age
  this.sayHello = function () {
    console.log('Hello my name is ' + this.name + ' and I am' + this.age + ' years old.')
  }
}

// 使用构造函数创建对象实例
var person1 = new Person('Alice', 30)
var parson2 = new Person('Bob', 25)

// 调用对象实例的方法
person1.sayHello()
person2.sayHello()

它的实现非常简单,只需要定义一个构造函数,然后通过 new 关键字来创建对象实例。

但是需要注意的是,尽管在构造函数内部定义的方法可以访问构造函数内部的属性,但是每次创建对象实例时,都会重新创建一组新的方法,可能会占用较多内存。如果方法可以在所有实例之间共享,应该将它们定义在构造函数的原型对象上,以节省内存。

4. 模块模式

一种用于组织和封装代码的模式,它使用闭包来创建私有作用域,从而避免全局命名空间污染,并提供一种方式来导出公共接口供外部访问。模块模式旨在解决 JavaScript 中的模块化和封装性的问题,使得代码更易于维护和拓展。

优点:

  1. 封装性: 使用模块模式可以创建私有作用域,将变量和函数封装在模块内部,避免了全局命名空间的污染,提供了代码的可维护性和安全性。
  2. 代码组织: 模块模式可以将相关的功能组织在一起,使得代码结构更清晰、更易于理解。
  3. 可重用性: 模块模式可以将一组相关的功能封装在一个模块中,并在其他地方重复使用,提高了代码的可重用性。
  4. 适应性: 模块模式适用于各种规模的项目,可以根据需要创建不同大小的模块。

缺点:

  1. 可测试性: 私有作用域难以被测试,因为无法直接访问模块内部的私有变量和函数,需要借助特殊的测试工具或技术来测试模块的私有部分。
  2. 增加复杂度: 使用模块模式会增加一定的复杂度,特别是对于初学者来说,可能会增加理解和学习的难度。
  3. 内存占用: 每个模块都会创建一个闭包,可能会导致内存占用增加,特别是大型项目中。

实现方式:

    const Module = (function () {
  // 私有变量和函数
  let privateVar = 0

  function privateFunction() {
    console.log('Private Function')
  }

  // 公共接口
  return {
    publicVar: 1,
    publicFunction: function () {
      console.log('Public Function')
    }
  }
})()

// 使用模块
Module.publicFunction()
console.log(Module.publicVar)

在上面的例子中,Module 是一个使用模块模式实现的模块,它包含了私有变量 privateVar 和私有函数 privateFunction,以及公共接口 publicVarpublicFunction。通过返回一个包含公共接口的对象,外部可以访问模块提供的公共方法和变量,但无法直接访问模块内部的私有吧部分。

5. 观察者模式

用于定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。在观察者模式中,有两种角色:观察者(observer)和被观察者(Subject)。

优点:

  1. 解耦性: 观察者模式将目标和观察者之间的关系解耦,使得它们可以独立变化,降低了对象之间的耦合性。
  2. 拓展性: 可以根据需要动态地增加和删除观察者,而不影响被观察者或其他观察者。
  3. 灵活性: 被观察者只关注观察者的接口,而不需要知道具体观察者的实现,使得系统更加灵活。

缺点:

  1. 可能引起循环调用: 如果观察者之间存在循环依赖,可能会导致循环调用,增加程序的复杂性。
  2. 可能导致性能问题: 当被观察者对象有大量的观察者时,通知每个观察者可能会导致性能问题。

实现方式:

    // 观察者对象
class Observer {
  update() {
    // 处理被观察者发出的通知
  }
}

// 被观察者对象
class Subject {
  constructor() {
    this.observers = []
  }
  addObserver(observer) {
    this.observers.push(observer)
  }
  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer)
  }
  notifyObservers() {
    this.observers.forEach(observer => observer.update())
  }
}

// 使用示例
const subject = new Subject()
const observer1 = new Observer()
const observer2 = new Observer()

subject.addObserver(observer1)
subject.addObserver(observer2)

// 被观察者状态发生变化,通知观察者
subject.notifyObservers()
分享:
返回文章列表