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

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

一、设计模式原则

  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的广播事件

五、结尾

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

相关推荐
APP 肖提莫几秒前
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
java·前端·算法
问道飞鱼12 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
k093313 分钟前
vue中proxy代理配置(测试一)
前端·javascript·vue.js
傻小胖15 分钟前
React 脚手架使用指南
前端·react.js·前端框架
信徒_27 分钟前
常用设计模式
java·单例模式·设计模式
程序员海军27 分钟前
2024 Nuxt3 年度生态总结
前端·nuxt.js
m0_7482567837 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
web135085886351 小时前
前端node.js
前端·node.js·vim
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js