前端-设计模式

发布于:2025-07-13 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、使用过哪些设计模式?

1、单例模式

一个类只要一个实例,并提供全局访问点来获取实例

2、工厂模式

根据工厂方式创建对象,而不是通过new操作符,这样隐藏了具体实现,并根据需要创建所需类型的对象

3、观察者模式

定义了一种一对多的依赖关系发,当一个对象的状态发生变化时,它的所有依赖着(观察者)都会受到消息并更新

4、装饰器模式

动态的将责任附加到对象上,通过将对象包装在装饰器对象中,可以在运行时为对象添加新的行为

5、适配器模式

将一个类的接口转换为客户端所期望的一个接口,使得原本由于接口不匹配而无法在一起工作的类可以协同工作

二、设计模式分类

  • 创建型模式:工厂模式、单例模式、原型模式
  • 结构型模式:适配器模式、装饰器模式、代理模式、桥接模式
  • 行为式模式:观察者模式、策略模式、责任链模式、模板方法模式等

三、观察者模式和发布订阅模式

观察者模式:一个对象(观察者)订阅另一个对象(主题),当主题被激活的时候,触发观察者里面的事件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发布订阅模式:订阅者将想要订阅的事件注册到调度中心,当发布者发布事件到调度中心(事件被触发),再由调度中心统一调度订阅中心注册到调度中心的处理代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

区别:

角色数量不同:

  • 观察者模式:只有两个:观察者和被观察者
  • 发布订阅:发布者、订阅者、调度中心

使用场景不同:

  • 观察者适用于单个应用内部使用
  • 发布订阅模式适用于跨应用场景
手写发布订阅模式
class EventEmitter {
  // 初始化一个空对象,用于存储事件名称及其对应的回调函数数组
  constructor() {
    this.events = {};
  }

  // 注册事件
  on(eventName, callback) {
    if (typeof callback !== "function") {
      throw new Error("callback must be a function");
    }
    // 如果事件不存在,创建一个新数组,存在则直接使用现有数组并进行添加
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(callback);
  }

  // 发布事件
  // args为调用emit时传入的所有参数
  emit(eventName, ...args) {
    // 检查事件是否存在,不存在直接返回
    if (this.events[eventName]) {
        // 避免迭代过程中数组修改
        [...this.events[eventName]].forEach(callback => {
            callback.apply(this, args);
        })
    }
  }

  // 取消订阅
  off(eventName, callback) {
    // 事件不存在,直接返回
    if (!this.events[eventName]) return;
    this.events[eventName] = this.events[eventName].filter(
        // 过滤回调数组
        cb => cb !== callback
    );
  }
  
  // 清除事件
  clear(eventName) {
    if (this.events[eventName]) {
        delete this.events[eventName];
    }
  }
  clearAll() {
    this.events = {};
  }
}

上述发布改为异步

class EventEmitter {
  constructor() {
    this.events = {};
  }

  // 注册事件
  on(eventName, callback) {
    if (typeof callback !== "function") {
      throw new Error("callback must be a function");
    }
    this.events[eventName] = this.events[eventName] || [];
    this.events[eventName].push(callback);
  }

  // 发布事件(支持同步/异步)
  emit(eventName, isAsync = false, ...args) {
    if (!this.events[eventName]) return;

    for (const callback of [...this.events[eventName]]) {
      if (isAsync) {
        Promise.resolve().then(() => {
          try {
            callback.apply(this, args);
          } catch (error) {
            console.error(`Async error in event "${eventName}":`, error);
          }
        });
      } else {
        try {
          callback.apply(this, args);
        } catch (error) {
          console.error(`Error in event "${eventName}":`, error);
        }
      }
    }
  }

  // 只执行一次
  once(eventName, callback) {
    const wrapper = (...args) => {
      callback.apply(this, args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
  }

  // 取消订阅
  off(eventName, callback) {
    if (!this.events[eventName]) return;
    this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
  }

  // 清除某个事件
  clear(eventName) {
    delete this.events[eventName];
  }

  // 清除所有事件
  clearAll() {
    this.events = {};
  }
}

module.exports = EventEmitter;

四、什么是MVVM?和NVC、MVP有什么区别

这三者都是常见的软件架构设计模式,主要通过分离关注点的方式来组织代码的结构,优化开发效率,

在之前的项目开发中,使用单页应用时,往往一个路由页面对应一个脚本文件,所有的页面逻辑都放在一个脚本文件中。页面的渲染、数据的获取、对用户事件的响应所有应用逻辑就混合一起,因此在项目复杂时候,整个文件就变得冗长,混乱,不利于项目的开发和维护

MVC通过分离model、view和Controller的方式组织代码结构,其中view负责页面的显示逻辑,model负责存储页面的业务数据以及对数据的操作,并且View和Model使用了观察者模式,当Model层发生变化时就会通知有关view层更新应用。Controller主要负责用户与应用的响应操作,当产生交互时,controller中的事件触发器就开始工作,通过调用model层实现对其修改,然后再去通知View层。

MVP和MVC的不同在于Presenter和Controller。在MVC中使用观察者模式,来实现当Model层发生变化时通知view层进行更新。这样View和model层耦合在一起了,就会导致代码混乱。MVP的模式则是通过Presenter来实现对View和Model层的解耦。在MVC在,Controller只知道Model的接口,因此没有白发控制View的更新,在MVP中,View的接口暴漏给了Presenter,因此可以在里面将Model的变化和View的变化绑定一起,实现同步更新。

MVVM中的VM是指ViewModel。与MVP思想一样,只是通过双向绑定的方式将View和Model的同步更新自动化了。其实就是将Presenter的工作给自动化了。

React 并不是严格意义上的 MVVM 框架,但它实现了类似 MVVM 的核心思想:组件本身相当于 ViewModel,负责管理状态和处理逻辑;JSX 是 View 的声明式描述,随着组件的 stateprops 变化自动更新;而数据模型可以是组件内部状态,也可以通过 Redux 等库统一管理,相当于 Model。React 通过这种单向数据流的机制,实现了 View 与数据之间的绑定和同步,具备响应式 UI 和良好的组件解耦能力,整体架构非常贴近 MVVM 模式。区别于VUE,通过数据劫持和发布订阅模式实现这个功能。

五、单例模式

  • 单例就是保证一个类只有一个实例,并且提供一个访问该全局访问点

1、哪里使用了单例模式

单例模式保证只有一个全局计数器对象存在避免多个实例导致状态不一致

  • 网站的计数器:

    class Counter {
      constructor() {
        if (Counter.instance) return Counter.instance;
        this.count = 0;
        Counter.instance = this; // 唯一实例
      }
    
      increment() {
        this.count += 1;
        return this.count;
      }
    
      getCount() {
        return this.count;
      }
    }
    
    const counter = new Counter();
    // 不调用 Object.freeze(),保持可变
    module.exports = counter;
    

    尝试使用 Object.freeze() 来防止单例被篡改,但这也导致属性如 count 变成只读。为保持数据状态可变性,我选择不冻结整个对象,而是通过模块导出控制实例唯一性或者Object.freeze(Counter.prototype);

  • 应用程序的日志应用

  • 多线程的线程池

  • windows的任务管理器/回收站

2、优缺点

优点:

  • 因为只有一个实例,所有对单例类的所有实例化都是一个实例,防止其它对象对自己的的实例化,确保所有对象访问一个实例
  • 在系统内存中只有一个实例对象,节约系统资源,尤其在需要频繁创建和销毁的对象时
  • 允许可变数目的实例
  • 避免对共享资源的多重占用

缺点:

  • 不适应于变化的对象
  • 单例类的职责过重,一定程度上违背了“单一职责”
  • 单例模式没有抽象层,就导致了扩展的困难
  • 其他负面;如数据库连接池对象设计为单例类,可能导致共享连接池对象的程序过多而出现的连接池溢出,且实例对象长时间不被利用,会被任务垃圾进行回收

六、工厂模式

创建对象的最佳方式,创建的对象时不会对客户端暴漏创建逻辑,并且通过使用一个共同接口来指向新创建对象,实现创建者和调用者分离,工厂方式有简单工厂,工厂方法,抽象工厂模式

好处:

  • 用工厂方法代替new操作
  • 降低程序的耦合性,为后期维护提高便利

七、设计模式的原则

  • 单一职责:一个方法负责一件事
  • 接口隔离原则:使用多个隔离的接口,降低类间的耦合度
  • 依赖倒转原则:面向接口编程
  • 迪米特法则:一个对象应当对其它对象有尽可能少的了解,类间解耦
  • 开放封闭原则:扩展软件实体解决需求变化
  • 里氏代换原则:使用的基类可以在任何地方使用继承的子类,完美替换基类

网站公告

今日签到

点亮在社区的每一天
去签到