面试中js常问的12个设计模式

工厂模式

原理:

工厂模式通过使用工厂方法来创建对象,而不是直接使用new关键字。工厂方法根据输入参数的不同,决定创建哪个具体的对象实例,并将其返回。

代码实现:

javascript 复制代码
// 定义一个产品类
class Product {
  constructor(name) {
    this.name = name;
  }

  display() {
    console.log(`Product: ${this.name}`);
  }
}

// 定义工厂类
class Factory {
  createProduct(name) {
    return new Product(name);
  }
}

// 使用工厂创建对象
const factory = new Factory();
const product1 = factory.createProduct('Product 1');
const product2 = factory.createProduct('Product 2');

product1.display(); // 输出: Product: Product 1
product2.display(); // 输出: Product: Product 2

使用场景:

  • 当需要创建多个相似的对象时。
  • 当对象创建过程复杂或需要隐藏创建逻辑时。
  • 当希望通过一个公共的接口来创建对象时。

优点:

  • 将对象的创建与使用代码分离,客户端只需关注接口而不需要关心具体的对象创建过程。
  • 可以通过工厂方法来创建不同类型的对象,提供灵活性和可扩展性。

单例模式

原理:

单例模式确保一个类只有一个实例,并提供全局访问点以获取该实例。它通过私有化构造函数,限制外部直接创建对象,并提供一个静态方法来获取或创建唯一的实例。

代码实现:

ini 复制代码
class Singleton {
  static instance = null;

  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }
}

// 获取实例
const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // 输出: true

使用场景:

  • 当只需要一个全局对象来协调系统中的操作时。
  • 当需要频繁访问同一个对象实例时。
  • 当需要限制一个类只能有一个实例时。

优点:

  • 提供了对唯一实例的全局访问,方便共享对象。
  • 避免了重复创建实例的开销,节省了内存和资源

观察者模式

原理:

观察者模式定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,它的所有依赖者(观察者)都会被通知和更新。

代码实现:

javascript 复制代码
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());
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }

  update() {
    console.log(`Observer ${this.name} has been notified.`);
  }
}

// 创建主题和观察者
const subject = new Subject();
const observer1 = new Observer('1');
const observer2 = new Observer('2');

// 注册观察者
subject.addObserver(observer1);
subject.addObserver(observer2);

// 通知观察者
subject.notifyObservers();

使用场景:

  • 当一个对象的变化需要通知其他对象,以便它们可以做出相应的响应时。
  • 当对象之间的耦合度需要降低,使得它们可以独立地交互时。

优点

  • 实现了对象之间的松耦合,被观察者和观察者可以独立地演化和变化。
  • 可以轻松添加或移除观察者,以实现动态的发布-订阅机制。

发布-订阅模式

原理:

发布-订阅模式类似于观察者模式,但是发布者(或称为主题)不会直接通知特定的订阅者,而是通过消息代理(或称为事件总线)来分发和传递消息。订阅者可以根据自身的需求订阅感兴趣的消息。

代码实现:

javascript 复制代码
class EventBus {
  constructor() {
    this.subscribers = {};
  }

  subscribe(eventName, callback) {
    if (!this.subscribers[eventName]) {
      this.subscribers[eventName] = [];
    }
    this.subscribers[eventName].push(callback);
  }

  unsubscribe(eventName, callback) {
    if (this.subscribers[eventName]) {
      this.subscribers[eventName] = this.subscribers[eventName].filter(
        (cb) => cb !== callback
      );
    }
  }

  publish(eventName, data) {
    if (this.subscribers[eventName]) {
      this.subscribers[eventName].forEach((callback) => callback(data));
    }
  }
}

// 创建事件总线
const eventBus = new EventBus();

// 订阅事件
const callback1 = (data) => console.log(`Subscriber 1 received: ${data}`);
const callback2 = (data) => console.log(`Subscriber 2 received: ${data}`);
eventBus.subscribe('event1', callback1);
eventBus.subscribe('event1', callback2);

// 发布事件
eventBus.publish('event1', 'Hello, subscribers!');

使用场景:

  • 当一个对象的状态变化需要通知多个订阅者时。
  • 当需要将发布者和订阅者解耦,使它们可以独立地演化时。
  • 当希望在系统中引入中介层以提供更灵活的消息传递机制时。

优点

  • 解耦了发布者和订阅者,使它们可以独立地交互。
  • 提供了更灵活的消息传递机制,可以实现更复杂的事件处理逻辑。

原型模式

原理:

原型模式通过克隆现有对象来创建新对象,而不是依赖显式的实例化过程。每个对象都可以作为另一个对象的原型,新对象会继承原型对象的属性和方法。

代码实现:

javascript 复制代码
class Prototype {
  constructor(name) {
    this.name = name;
  }

  clone() {
    return Object.create(Object.getPrototypeOf(this));
  }
}

// 创建原型对象
const prototype = new Prototype('Prototype');

// 克隆对象
const clone1 = prototype.clone();
const clone2 = prototype.clone();

console.log(clone1.name); // 输出: Prototype
console.log(clone2.name); // 输出: Prototype

使用场景:

  • 当创建对象的过程比较昂贵或复杂时,而且新对象的创建与现有对象的状态无关时。
  • 当希望通过修改原型对象来影响所有克隆对象时。
  • 当需要避免使用new关键字直接实例化对象时。

优点

  • 避免了创建对象的昂贵或复杂过程,提高了性能和效率。
  • 可以通过修改原型对象来影响所有克隆对象,实现了对象状态的批量修改。

适配器模式

原理:

适配器模式将一个类的接口转换成另一个接口,以满足客户端的需求。它通过创建一个适配器类来实现接口转换,并在适配器类中调用被适配类的方法。

代码实现:

javascript 复制代码
class Adaptee {
  specificRequest() {
    return 'Specific request';
  }
}

class Adapter {
  constructor(adaptee) {
    this.adaptee = adaptee;
  }

  request() {
    return this.adaptee.specificRequest();
  }
}

// 使用适配器
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // 输出: Specific request

使用场景:

  • 当需要将一个已有类的接口转换成另一个接口时。
  • 当希望通过一个统一的接口来使用多个不兼容的类时。
  • 当需要在不影响现有代码的情况下,对已有类的方法进行扩展或修改时。

优点

  • 可以将已有类与新代码进行无缝衔接,使它们能够协同工作。
  • 可以实现对象之间的接口转换,提供了灵活性和可扩展性。

装饰者模式

原理:

装饰者模式动态地给对象添加新的行为或功能,同时不改变其原始类结构。它通过创建一个装饰器类来包装原始对象,并在装饰器类中添加额外的行为。

代码实现:

javascript 复制代码
class Component {
  operation() {
    return 'Component operation';
  }
}

class Decorator {
  constructor(component) {
    this.component = component;
  }

  operation() {
    return `${this.component.operation()} + Decorator operation`;
  }
}

// 使用装饰者
const component = new Component();
const decorator = new Decorator(component);
console.log(decorator.operation()); // 输出: Component operation + Decorator operation

使用场景:

  • 当需要在不改变现有对象结构的情况下,动态地给对象添加新的行为时。
  • 当希望通过透明的方式为对象添加功能,而不影响其使用方式和客户端代码时。
  • 当不适合使用子类来扩展对象功能时。

优点

  • 可以透明地扩展对象的功能,而不会影响客户端代码。
  • 允许通过装饰器类组合和嵌套多个装饰器,实现复杂的功能组合。

策略模式

原理:

策略模式定义了一系列算法,将它们封装成独立的可互换的策略对象,并使得客户端可以在运行时动态地选择使用不同的策略。客户端通过与策略对象进行交互来实现不同的行为。

代码实现:

scala 复制代码
class Strategy {
  execute() {
    // 策略执行的具体操作
  }
}

class ConcreteStrategy1 extends Strategy {
  execute() {
    console.log('Strategy 1');
  }
}

class ConcreteStrategy2 extends Strategy {
  execute() {
    console.log('Strategy 2');
  }
}

// 使用策略
const strategy1 = new ConcreteStrategy1();
const strategy2 = new ConcreteStrategy2();

strategy1.execute(); // 输出: Strategy 1
strategy2.execute(); // 输出: Strategy 2

使用场景:

  • 当需要在多个算法或行为之间进行动态选择时。
  • 当希望将算法的实现与使用它的客户端代码分离,以便它们可以独立地演化和修改时。
  • 当不希望使用大量的条件语句来处理不同的情况时。

优点

  • 实现了算法的封装和多态性,可以根据需要灵活地切换算法。
  • 将算法的实现与使用它的客户端代码分离,使得它们可以独立演化和修改。

模块模式

原理:

模块模式使用函数作用域和闭包来封装和组织代码,实现模块化和私有性。它通过返回一个包含公共方法和属性的对象,来实现对外部的封装。

代码实现:

javascript 复制代码
const module = (function() {
  let privateVariable = 'Private';

  function privateMethod() {
    console.log('Private method');
  }

  return {
    publicVariable: 'Public',
    publicMethod: function() {
      console.log('Public method');
    }
  };
})();

console.log(module.publicVariable); // 输出: Public
module.publicMethod(); // 输出: Public method

使用场景:

  • 当希望将相关的方法和属性封装在一个单独的对象中时。
  • 当希望限制对方法和属性的访问,并保持私有性时。
  • 当需要实现模块化,避免全局命名冲突和污染时。

优点

  • 将相关的方法和属性封装在一个单独的对象中,提供了组织和管理代码的方式。
  • 通过闭包实现了私有性,可以隐藏内部实现细节,防止外部访问和修改。

代理模式

原理:

代理模式为一个对象提供一个代理或占位符,并控制对其的访问。代理对象可以在访问被代理对象之前或之后添加额外的逻辑,如延迟加载、权限控制、缓存等。

代码实现:

javascript 复制代码
class RealSubject {
  request() {
    console.log('Real subject request');
  }
}

class Proxy {
  constructor(realSubject) {
    this.realSubject = realSubject;
  }

  request() {
    // 在调用真实对象方法之前或之后执行额外操作
    console.log('Proxy request');
    this.realSubject.request();
  }
}

// 使用代理
const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);
proxy.request(); // 输出: Proxy request, Real subject request

使用场景:

  • 当需要在访问对象之前或之后执行额外操作时。
  • 当希望通过代理控制对对象的访问权限时。
  • 当需要延迟加载对象或实现缓存等功能时。

优点:

  • 可以在访问对象之前或之后执行额外操作,如延迟加载、权限控制、缓存等。
  • 提供了对真实对象的访问控制,可以限制对对象的直接访问。

迭代器模式

原理:

迭代器模式提供了一种访问集合对象元素的方式,而无需暴露集合的内部结构。它将迭代逻辑封装在迭代器对象中,客户端通过迭代器来遍历集合。

代码实现:

kotlin 复制代码
// 定义集合对象
class Collection {
  constructor() {
    this.items = [];
  }
  
  addItem(item) {
    this.items.push(item);
  }
  
  getIterator() {
    return new Iterator(this.items);
  }
}

// 定义迭代器对象
class Iterator {
  constructor(collection) {
    this.collection = collection;
    this.index = 0;
  }
  
  hasNext() {
    return this.index < this.collection.length;
  }
  
  next() {
    return this.collection[this.index++];
  }
}

// 使用迭代器遍历集合
const collection = new Collection();
collection.addItem("Item 1");
collection.addItem("Item 2");
collection.addItem("Item 3");

const iterator = collection.getIterator();
while (iterator.hasNext()) {
  console.log(iterator.next());
}

使用场景:

  • 当集合对象的内部结构可能经常变化时,使用迭代器可以减少对客户端代码的影响。
  • 当需要对集合对象进行不同类型的遍历时,迭代器提供了统一的接口,使得遍历逻辑更加灵活和可扩展。
  • 当需要在遍历过程中对集合元素进行筛选、过滤或转换时,可以通过迭代器来实现。

优点:

  • 将遍历集合的责任从客户端代码中抽离出来,简化了客户端代码。
  • 隐藏了集合的内部结构,提供了更好的封装性和安全性。
  • 支持不同类型的集合,提供了统一的迭代接口。

状态模式

原理:

状态模式允许对象在内部状态发生改变时改变其行为,看起来就像是对象类发生了改变。它将每个状态封装在一个独立的类中,并允许对象在不同状态之间切换。

代码实现:

scala 复制代码
// 定义状态接口
class State {
  handle(context) {
    // 默认实现
  }
}

// 定义具体状态类
class ConcreteStateA extends State {
  handle(context) {
    console.log("State A");
    context.setState(new ConcreteStateB());
  }
}

class ConcreteStateB extends State {
  handle(context) {
    console.log("State B");
    context.setState(new ConcreteStateA());
  }
}

// 定义上下文类
class Context {
  constructor() {
    this.state = new ConcreteStateA();
  }
  
  setState(state) {
    this.state = state;
  }
  
  request() {
    this.state.handle(this);
  }
}

// 使用状态模式
const context = new Context();
context.request();  // 输出 "State A"
context.request();  // 输出 "State B"
context.request();  // 输出 "State A"

使用场景:

  • 当一个对象的行为取决于其内部状态,并且在不同状态下具有不同行为时,可以使用状态模式来管理状态转换和行为。
  • 当需要在运行时根据条件动态地改变对象的行为时,状态模式提供了一种优雅的方式来实现。
  • 当对象有大量的条件语句,而且随着状态的增加会变得更加复杂时,可以使用状态模式来简化代码结构。

优点:

  • 将对象的状态和行为封装在独立的类中,提高了代码的可读性和可维护性。
  • 避免了使用大量的条件语句来处理不同的状态,简化了代码结构。
  • 新增或修改状态变得更加容易,不会对其他状态产生影响。
相关推荐
GIS之路2 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug6 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121388 分钟前
React面向组件编程
开发语言·前端·javascript
学历真的很重要8 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
持续升级打怪中29 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路33 分钟前
GDAL 实现矢量合并
前端
hxjhnct35 分钟前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端