前端常用的设计模式(真实场景例子)

本篇内容适合对设计模式有些了解,但想更进一步了解设计模式在实际场景应用的读者。

一、设计模式原则

  1. 单一职责原则:一个函数只做一件事,如果功能过于复杂就拆分开

  2. 开发/封闭原则:对扩展开放,对修改封闭;增加需求时,扩展新代码,而非修改已有代码

  3. 里氏替换原则:子类能覆盖父类

  4. 接口隔离原则:保持接口的单一独立

  5. 依赖倒转原则:面向接口编程,依赖于抽象而不依赖于具体;使用方只关注接口而不关注具体类的实现

二、创建型设计模式

1、工厂模式

实质:一个工厂对象负责创建其他对象,而具体的创建逻辑由子类决定

应用:根据参数,调用对应的插件

js 复制代码
// 统计插件
class AnalyticsPlugin {
    init() {
      console.log("初始化统计插件");
    }
  }
  
  // 日志插件
  class LoggerPlugin {
    init() {
      console.log("初始化日志插件");
    }
  }
  
  // 工厂对象
  class PluginFactory {
    createPlugin(types) {
      types.forEach((type) => {
        switch (type) {
          case "analytics":
            this.init(AnalyticsPlugin);
            break;
          case "logger":
            this.init(LoggerPlugin);
            break;
          default:
            throw new Error("Invalid plugin type.");
        }
      });
    }
  
    //初始化插件
    init(Plugin) {
      const plugin = new Plugin();
      plugin.init();
    }
  }
  
  // 使用示例
  const pluginFactory = new PluginFactory();
  //初始化统计插件,初始化日志插件
  pluginFactory.createPlugin(["analytics", "logger"]);

2、单例模式

实质:一个类只有一个实例

应用:全局状态管理器vuex和redux,单例弹窗

js 复制代码
// 实现单体模式弹窗
var createWindow = (function () {
  var div;
  return function () {
    if (!div) {
      div = document.createElement("div");
      div.innerHTML = "我是弹窗内容";
      div.style.display = "none";
      document.body.appendChild(div);
    }
    return div;
  };
})();
document.getElementById("btn").onclick = function () {
  var win = createWindow();
  win.style.display = "block";
};

3、原型模式

实质:通过复制现有对象来创建新对象

应用:JavaScript的原型

js 复制代码
function Shape() {
  this.name = "Shape";
}

Shape.prototype.draw = function () {
  console.log(this.name + " is drawing...");
};

function Rectangle() {
  Shape.call(this);
  this.name = "Rectangle";
}

Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

function Circle() {
  Shape.call(this);
  this.name = "Circle";
}

Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

// 测试Rectangle和Circle的继承关系
let rectangle = new Rectangle();
let circle = new Circle();

rectangle.draw(); // 输出 "Rectangle is drawing..."
circle.draw(); // 输出 "Circle is drawing..."

三、结构型设计模式

1、适配器模式

实质:将一个类的接口转换成客户端所期望的另一个接口。

应用:vue的computed,react的useMemo

computed:

js 复制代码
<template>
  <div>
    {{ message }}
    {{ reversedMessage }}
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";

const message = ref("hello vue");

const reversedMessage = computed(() =>
  message.value.split("").reverse().join("")
);
</script>

useMemo:

js 复制代码
import { useMemo, useState } from "react";
function App() {
  const [message] = useState("hello react");

  const reversedMessage = useMemo(
    () => message.split("").reverse().join(""),
    [message]
  );
  return (
    <div className="App">
      {message}
      {reversedMessage}
    </div>
  );
}

export default App;

2、装饰器模式

实质:动态地给对象添加一些额外的功能

应用:React的高阶组件(HOC)

js 复制代码
import { useState, useEffect } from "react";

//高阶组件
function withLoading(WrappedComponent) {
  return function ({ isLoading, ...props }) {
    if (isLoading) {
      return <div>Loading...</div>;
    } else {
      return <WrappedComponent {...props} />;
    }
  };
}

//业务组件
function DataList({ data }) {
  return (
    <ul>
      {data.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

const DataListWithLoading = withLoading(DataList);

function App() {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      setData([1, 2, 3]);
      setIsLoading(false);
    }, 2000);
  }, []);

  return (
    <div>
      <h1>Data List</h1>
      <DataListWithLoading data={data} isLoading={isLoading} />
    </div>
  );
}

export default App;

3、代理模式

实质:创建一个代理对象来控制对另一个对象的访问

应用:使用ES6的Proxy

js 复制代码
let obj = {
  a: 1,
  b: 2,
};

let proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log("监听get");
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("触发set");
    Reflect.set(target, key, value, receiver);
  },

  deleteProperty(target, key) {
    console.log("监听删除");
    Reflect.deleteProperty(target, key);
    //如果抛出错误或者返回false,当前属性就无法被delete命令删除
    return true;
  },
  has(target, key) {
    console.log("监听has");
    return Reflect.has(target, key);
  },
});

proxy.a; //监听get

proxy.a = 4; //触发set

delete proxy.b; //监听删除

"a" in proxy; //监听has

四、行为型设计模式

1、观察者模式

实质:一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知

应用:比起观察者模式,更常用发布-订阅模式实现跨组件通信

js 复制代码
class Observer {
  constructor() {
    this.all = new Map();
  }

  on(type, handler) {
    const handlers = this.all.get(type);
    if (handlers) {
      handlers.push(handler);
    } else {
      this.all.set(type, [handler]);
    }
  }

  off(type, handler) {
    const handlers = this.all.get(type);
    if (handlers) {
      if (handler) {
        const i = handlers.indexOf(handler);
        if (i > -1) {
          handlers.splice(i, 1);
        }
      } else {
        this.all.set(type, []);
      }
    }
  }

  emit(type, args) {
    const handlers = this.all.get(type);
    if (handlers) {
      handlers.forEach((handler) => handler(args));
    }
  }
}

2、策略模式

实质:根据不同参数可以命中不同的策略

应用:在对象中定义一系列算法,随取随用

js 复制代码
//表单校验
var strategies = {
  /* 不为空 */
  isNonEmpty: function (value, errorMsg) {
    if (value === "") {
      return errorMsg;
    }
  },
  /* 限制最小长度 */
  minLength: function (value, length, errorMsg) {
    if (value.length < length) {
      return errorMsg;
    }
  },
  /* 手机号码格式 */
  isMobile: function (value, errorMsg) {
    let rule = /^1[3|5|8][0-9]{9}$/;
    if (!rule.test(value)) {
      return errorMsg;
    }
  },
};

3、模板方法模式

实质:父类定义公共的行为和逻辑,在子类中实现具体的细节

应用:抽象类

js 复制代码
//抽象类
abstract class DefinePlugin {
    public name: string;
  
    constructor(name: string) {
      this.name = name;
    }
    //抽象方法
    abstract monitor(): void;
  }
  
  //子类
  class BehaviorPlugin extends DefinePlugin {
    constructor() {
      super("behavior");
    }
    
    //定义具体的实现
    monitor(): void {
      ["click"].forEach(function (eventType) {
        document.addEventListener(
          eventType,
          (e) => {
            //处理点击事件
          },
          true
        );
      });
    }
  }

4、责任链模式

实质:允许对象在链上依次处理请求

应用:数据验证

js 复制代码
/* 验证url */
class UrlValidator {
  setNext(validator) {
    this.nextValidator = validator;
  }

  validate(data) {
    if (!data.url) {
      console.error("url不存在");
      return false;
    } else if (this.nextValidator) {
      return this.nextValidator.validate(data);
    } else {
      return true;
    }
  }
}

/* 验证缓存数量 */
class NumberValidator {
  setNext(validator) {
    this.nextValidator = validator;
  }

  validate(data) {
    if (!data.count) {
      console.error("请输入数量");
      return false;
    } else if (data.count && data.count < 0) {
      console.error("请输入非负数");
    } else if (this.nextValidator) {
      return this.nextValidator.validate(data);
    } else {
      return true;
    }
  }
}

/* 责任链模式 */
const urlValidator = new UrlValidator();
const maxValidator = new NumberValidator();

/* 指定节点在责任链中的顺序 */
urlValidator.setNext(maxValidator);

urlValidator.validate({
  url: "1",
  count: -1, //请输入非负数
});

5、中介者模式

实质:对象和对象之间借助第三方中介者进行通信

应用:聊天室

js 复制代码
/* 中介者 */
class Mediator {
  constructor() {
    this.list = [];
  }

  add(data) {
    this.list.push(data);
  }

  /*  广播事件 */
  broadcast(source, message) {
    this.list.filter((o) => o !== source).forEach((o) => o.receive(message));
  }
}

const mediator = new Mediator();

class User {
  constructor() {
    this.mediator = mediator;
    this.mediator.add(this);
  }

  send(message) {
    this.mediator.broadcast(this, message);
  }

  receive(message) {
    console.log(`Received message: ${message}`);
  }
}

// 使用中介者模式进行组件之间的通信
const user1 = new User();
const user2 = new User();

user1.send("Hello from User 1");
user2.send("Hi from User 2");

这里只是展示中介者示例,实际项目中,可借助WebSocket的广播事件

五、结尾

如果这篇文章对你有帮助,欢迎点赞收藏!!!

相关推荐
噢,我明白了13 分钟前
JavaScript 中处理时间格式的核心方式
前端·javascript
纸上的彩虹1 小时前
半年一百个页面,重构系统也重构了我对前端工作的理解
前端·程序员·架构
be or not to be2 小时前
深入理解 CSS 浮动布局(float)
前端·css
LYFlied2 小时前
【每日算法】LeetCode 1143. 最长公共子序列
前端·算法·leetcode·职场和发展·动态规划
老华带你飞2 小时前
农产品销售管理|基于java + vue农产品销售管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小徐_23332 小时前
2025 前端开源三年,npm 发包卡我半天
前端·npm·github
C_心欲无痕2 小时前
vue3 - 类与样式的绑定
javascript·vue.js·vue3
GIS之路3 小时前
GIS 数据转换:使用 GDAL 将 Shp 转换为 GeoJSON 数据
前端
JIngJaneIL3 小时前
基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
天天扭码3 小时前
以浏览器多进程的角度解构页面渲染的整个流程
前端·面试·浏览器