几种常见的javascript设计模式

news2024/12/27 7:28:54

摘要

最近开发HarmonyOSApp,使用的Arkts语言,此语言类似后端C#语言风格,同时兼顾写后端接口的我突然想总结一下近8年前端开发中无意中使用的设计模式,我们用到了却不知属于哪些,下面和大家分享一下。

什么是前端设计模式

前端的设计模式,其实就是一种可以在多处地方重复使用的代码设计方案, 只是不同的设计模式所能应用的场景有所不同。

通过这种设计模式可以帮助我们提高代码的可读性、可维护性与可扩展性。

前端设计模式分类

设计模式的话有高达二十多种,但本文章主要针对javascript相关的设计模式,因此我整理出来10种设计模式,并且进行分类总结。

即创建型、结构型和行为型

一、创建型

通过确定规则对代码进行封装,减少创建过程中的重复代码,并且对创建制定规则提高规范和灵活性。

1、单例模式
  • 确保一个类只有一个实例,并且提供一个访问它的全局访问点。
  • 由于只有一个实例,所以全局唯一性,并且更好地控制共享资源优化性能。
const test = {
  name: 'testName',
  age: '18',
};

export default test;
import test from './test';

console.log(test.name,test.age);  // 打印:testName,18

上述例子定义test并且export defaul暴露唯一的实例test,符合确保一个类只有一个实例,并且提供一个访问它的全局访问点原则。

其实单例模式有很多种实现方式,并且不同的实现方式有不同的适用场景,这种只是为了通过例子去理解这种设计模式的思想。

2、工厂模式
  • 对代码逻辑进行封装,只暴露出通用接口直接调用。
  • 降低耦合度,易于维护代码和提高后续扩展性。
// ------ 定义一个产品类 ------ 
class testProduct {
  constructor(productName) {
    this.productName = productName;
  }

  getName() {
    console.log(`产品名称: ${this.productName}`);
  }
}

// ----- 定义一个工厂函数 -------
function createProduct(name) {
  return new testProduct(name);
}

// 使用工厂函数创建对象
const test1 = createProduct('产品1');
const test2 = createProduct('产品2');

// 使用对象
test1.getName(); // 打印: 产品名称: 产品1
test2.getName(); // 打印: 产品名称: 产品2

上述例子定义一个工厂函数,逻辑代码是封装在testProduct类中,暴露出createProduct方法,调用时传入不同的参数返回不同的内容。

3、构造器模式
  • 定义一个通用的构造函数,然后方便多次传递参数调用。
  • 减少重复代码、提高可维护性和扩展性。
class testPerson {
  constructor(name, age,) {
    this.name = name;
    this.age = age;
  }

  introduce() {
    console.log(`姓名: ${this.name}, 年龄: ${this.age}`);
  }
}


const test1 = new testPerson('张三', 30);
test1.introduce(); //  姓名: 张三, 年龄: 30

const test2 = new testPerson('李四', 25);
test2.introduce(); // 输出: 姓名: 李四, 年龄: 25

结构型

主要是针对对象之间的组合。大概意思就是通过增加代码复杂度,从而提高扩展性和适配性。例如使代码兼容性更好、使某个方法功能更加强大。

1、适配器模式
  • 使某个类的接口有更强的适配性
  • 适配扩展后提高了复用性、降低耦合度并且增强了灵活性
// ------ 本来存在需要被适配的110V接口 ------
class Receptacle {
  plugIn() {
    console.log("110V 插座");
  }
}


//  ------ 适配者类 ------ 
class ForeignReceptacle {
  plugIn220V() {
    console.log("220V 插座");
  }
}


// ------ 用于适配的方法 ------
class VoltageAdapter {
  constructor(foreignReceptacle) {
    this.foreignReceptacle = foreignReceptacle;
  }
  plugIn() {
    this.foreignReceptacle.plugIn220V();
  }
}
// 创建110V设备
const receptacle = new Receptacle();
receptacle.plugIn(); // 打印输出: 110V 插座

// 创建220V设备
const foreignReceptacle = new ForeignReceptacle();

// 使用适配器将 220V 设备适配到 110V 插座
const adapter = new VoltageAdapter(foreignReceptacle);
adapter.plugIn(); // 打印输出: 220V 插座

正常使用Receptacle类时输出效果是110V,但我们需要配220V,那么使用定义的VoltageAdapter适配器把220VForeignReceptacle类适配到110VReceptacle类上。

2、装饰器模式
  • 创建一个对象去包裹原始对象,在不修改原始对象本身的情况下,动态给指定原始对象添加新的功能。
  • 不改动原函数的情况下方便动态扩展功能,可以复用现有函数增强灵活性。
// 基础函数
function getGreet(name) {
  console.log(`你好啊,${name}!`);
}

// 装饰器函数
function welcomePrefix(greetFunction) {
  return function(name) {
    console.log("欢迎啊");
    greetFunction(name);
  };
}

// 基础函数
getGreet("天天鸭"); // 打印: 你好啊,天天鸭!
// 添加 欢迎啊 前缀
const setWelcome = welcomePrefix(getGreet);
setWelcome("天天"); // 打印: 欢迎啊     
                    // 打印: 你好,天天!
3、代理模式
  • 给某个对象加一个代理对象,代理对象起到中介作用,中介对象在不改变原对象情况下添加功能。
  • 代理对象可以很方便实现拦截控制访问,并且不修改原对象提高代码复用率。
// 基础函数
function counterEvent() {
  let count = 0;
  return {
    setCount: () => {
      count++;
    },
    getCount: () => {
      return count;
    }
  };
}

// 代理函数
function countProxy() {
  const newCounter = counterEvent();
  return {
    setCount: () => {
      newCounter.setCount();
    },
    getCount: () => {
      return newCounter.getCount();
    }
  };
}

// 创建一个代理对象
const myCounter = countProxy();
// 触发增加
myCounter.setCount();
myCounter.setCount();
myCounter.setCount();
// 获取当前数
console.log(myCounter.getCount()); // 打印: 3

不让用户直接操作counterEvent函数,而是通过countProxy代理函数去操作counterEvent函数 。

这里只是举例这种代理模式的设计思想,如果在真实业务中间代理层其实可以很多逻辑操作。

行为型

1、观察者模式
  • 观察某个对象是否发生变化,如果发生变化就会通知所有订阅者,并做出相应操作,是一对一或一对多关系。
  • 有很强动态灵活性,可以轻松地添加或者移除观察者; 把观察者和被观察者解耦进行逻辑分离易于维护。
// 观察者
class Sub {
  constructor() {
    this.observers = [];
  }

  add(observer) { // 添加观察者到列表中
    this.observers.push(observer);
  }

  unadd(observer) {  // 从列表中移除观察者
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify(msg) {  // 通知所有观察者
    this.observers.forEach(observer => observer(msg));
  }
}

// 用于创建观察者
const createObs = (name) => {
  return (msg) => {
    console.log(`${name} 收到: ${msg}`);
  };
};

使用观察者模式代码: 被观察者Sub里面有add(添加)、unadd(移除)、notify(通知)观察者的方法,观察者createObs里面有接收通知的方法。

当我们用sub.add添加观察者之后,使用sub.notify发布消息所有的观察者都会收到通知。

sub.unadd移除一个观察者1后也同理,会不再收到通知。

// 创建一个被观察者
const sub = new Sub();

// 创建观察者
const obs1 = createObs("观察者1");
const obs2 = createObs("观察者2");

// 订阅被观察者
sub.add(obs1);
sub.add(obs2);

// 发布消息
sub.notify("你好鸭!"); // 观察者1和观察者2都收到: 你好鸭!
                              
// 移除观察者1
sub.unadd(obs1);

// 再次发布
sub.notify("你好鸭!"); // 只有观察者2收到: 你好鸭!
2、发布者订阅者模式
  • 这模式有点与观察者模式类似,但观察者模式是一对一或者一对多关系,而发布订阅模式是多对多关系,因此应用场景会有所不同。
  • 多对多关系有很强动态灵活性,可以多个订阅者,一个订阅者可以订阅多个事件; 把发布者和订阅者完全解耦提高灵活性和扩展性。
// 发布者
class Pub {
  constructor() {
    this.subobj = {};
  }

  subscribe(event, callback) {  // 订阅事件
    if (!this.subobj[event]) {
      this.subobj[event] = [];
    }
    this.subobj[event].push(callback);
  }

  unsubscribe(event, callback) {  // 移除订阅事件
    if (this.subobj[event]) {
      this.subobj[event] = this.subobj[event].filter(cb => cb !== callback);
    }
  }

  publish(event, data) { // 发布事件
    if (this.subobj[event]) {
      this.subobj[event].forEach(callback => callback(data));
    }
  }
}


// 创建一个发布者实例
const pub = new Pub();

// 订阅者回调函数
const subevent1 = (msg) => {
  console.log(`订阅者1 收到: ${msg}`);
};

const subevent2 = (msg) => {
  console.log(`订阅者2 收到: ${msg}`);
};

// 订阅事件
pub.subscribe("greet", subevent1);
pub.subscribe("greet", subevent2);

// 发布消息
pub.publish("greet", "你好鸭!"); // 订阅者1和订阅者2 收到: 你好鸭!

// 移除一个订阅者
pub.unsubscribe("greet", subevent1);

// 再次发布消息
pub.publish("greet", "你好鸭!"); //  只有订阅者2 收到: 你好鸭!

大概思路是定义一个Pub类,里面有subscribe(添加订阅事件)、unsubscribe(移除订阅事件)、 publish(通知发布事件)。new Publisher()创建发布者实例后可以添加、移除和发布事件。

3、命令模式
  • 把请求封装在对象里面整个传递给调用对象,使里面参数更加灵活方便扩展。
  • 使发送和接收者完全解耦独立易于数据维护、逻辑独立方便灵活处理、队列请求可以撤销操作。
// 接收者
class testLight {
  on() {
    console.log("打开灯了");
  }
  off() {
    console.log("关闭灯了");
  }
}

// 命令基类
class Comm {
  constructor(receiver) {
    this.receiver = receiver;
  }
}

// 具体命令
class LightOnComm extends Comm {
  execute() {
    this.receiver.on();
  }
}

class LightOffComm extends Comm {
  execute() {
    this.receiver.off();
  }
}

// 调用者
class RemoteControl {
  onButton(comm) {
    comm.execute();
  }
}

接收者testLight主要负责执行业务逻辑命令,即决定是否关灯;

LightOnCommLightOffComm继承基类Comm,实现execute()方法, 在xecute()方法中调用接收者的方法,然后分别调用onoff方法;

RemoteControl 类负责调用者的方法,即去调用execute()方法。

// 使用
const testlight = new testLight();
const lightOnComm = new LightOnComm(testlight);
const lightOffComm = new LightOffComm(testlight);
const remoteControl = new RemoteControl();

remoteControl.onButton(lightOnComm); // 输出: 打开灯了
remoteControl.onButton(lightOffComm); // 输出: 关闭灯了

创建一个testlight实例后,将其传递给LightOnCommLightOffComm的构造函数, 然后普创建了LightOnCommLightOffComm的实例。并将它们传递给RemoteControlonButton方法。

最后调用onButton方法时,就会调用相应命令的execute方法,从而执行相应的操作。

4、模版模式
  • 定义好整个操作过程的框架,框架中把每个步骤的逻辑独立处理。
  •  步骤独立分开管理,易于扩展功能维护代码。
class Game {
  constructor(obj) {
     
  }
  initGame() {
      console.log('初始化');
  }
  startGame() {
    console.log('游戏开始');
  }
  onGame() {
    console.log('游戏中');
  }
  endGame() {
    console.log('游戏结束');
  }
  personEntry() {
      this.initGame()
      this.startGame()
      this.onGame()
      this.endGame()
  }
}

这个Game类中把每个步骤的逻辑都放在对应步骤的方法中,独立管理互不影响。 添加或者减少步骤,只需要修改对应的方法即可。

总结

以上示例不是实际项目代码,只是为了方便理解设计模式而写的简单示例。

通过上面的示例,大家其实在开发中肯定使用过设计模式,希望大家平时闲暇时多阅读书籍提高自己的基础知识水平!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.4特征预处理(机器学习)

2.4特征预处理 2.4.1 什么是特征预处理 通过 一些转换函数将特征数据转换成更加适合算法模型的特征数据过程。 1 包含内容 数值型数据的无量纲化: 归一化 标准化 2 特征预处理API sklearn.preprocessing 为什么要进行归一化/标准化? 特征的单…

学习笔记052——Spring Boot 自定义 Starter

文章目录 Spring Boot 自定义 Starter1、自定义一个要装载的项目2、创建属性读取类 ServiceProperties3、创建 Service4、创建自动配置类 AutoConfigration5、创建 spring 工程文件6、将项目打成 jar 包7、jar 打包到本地仓库8、配置application.yml Spring Boot 自定义 Starte…

重学设计模式-建造者模式

本文介绍一下建造者模式,相对于工厂模式来说,建造者模式更为简单,且用的更少 定义 建造者模式是一种创建型设计模式,它使用多个简单的对象一步一步构建成一个复杂的对象。这种模式的主要目的是将一个复杂对象的构建过程与其表示…

复现SMPLify-X: Ubuntu22.04, Cuda-11.3, GPU=3090Ti

Env: 3090Ti CUDA 最低支持版本需要>cuda-11.1 Ubuntu 22.04 Installation: Installing CUDA11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run …

Milvus×OPPO:如何构建更懂你的大模型助手

01. 背景 AI业务快速增长下传统关系型数据库无法满足需求。 2024年恰逢OPPO品牌20周年,OPPO也宣布正式进入AI手机的时代。超千万用户开始通过例如通话摘要、新小布助手、小布照相馆等搭载在OPPO手机上的应用体验AI能力。 与传统的应用不同的是,在AI驱动的…

JAVA |日常开发中读写XML详解

JAVA |日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM(Document Object Model)方式读取 XML2.2 使用 SAX(Simple API for XML)方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…

GEOBench-VLM:专为地理空间任务设计的视觉-语言模型基准测试数据集

2024-11-29 ,由穆罕默德本扎耶德人工智能大学等机构创建了GEOBench-VLM数据集,目的评估视觉-语言模型(VLM)在地理空间任务中的表现。该数据集的推出填补了现有基准测试在地理空间应用中的空白,提供了超过10,000个经过人工验证的指…

南昌榉之乡托养机构解读:自闭症与看电视并无必然联系

在探讨自闭症的成因时,有人会问:自闭症是多看电视引起的吗?今天,就让我们来看看南昌榉之乡托养机构对此有何见解。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校,一直致力于为大龄自闭症患者提供专业的支持…

LabVIEW MathScript工具包对运行速度的影响及优化方法

LabVIEW 的 MathScript 工具包 在运行时可能会影响程序的运行速度,主要是由于以下几个原因: 1. 解释型语言执行方式 MathScript 使用的是类似于 MATLAB 的解释型语言,这意味着它不像编译型语言(如 C、C 或 LabVIEW 本身的 VI&…

基于eFramework车控车设中间件介绍

车设的发展,起源于汽车工业萌芽之初,经历了机械式操作的原始粗犷,到电子式调控技术的巨大飞跃,到如今智能化座舱普及,远程车控已然成为汽车标配,车设功能选项也呈现出爆发式增长,渐趋多元繁杂。…

使用 AWR 进行 Exadata 性能诊断 - 2018版

本文和之前的使用 AWR 进行 Exadata 性能诊断是非常类似的,理论部分几乎一样,但案例部分是不同的,其价值也在于此。前文是基于Exadata X10,本文是基于Exadata X5。当然,型号并不重要,重要的是分析过程。 本…

【AI系统】计算与调度

计算与调度 上一篇文章我们了解了什么是算子,神经网络模型中由大量的算子来组成,但是算子之间是如何执行的?组成算子的算法逻辑跟具体的硬件指令代码之间的调度是如何配合?这些内容将会在本文进行深入介绍。 计算与调度 计算与…

JavaSE学习心得(APL与算法篇)

常用APL和常见算法 前言 常用APL Math System Runtime Object ​编辑浅克隆 深克隆 Objects Biginteger 构造方法 成员方法 底层存储方式 Bigdecimal 构造方法 Bigdecimal的使用 底层存储方式 ​编辑正则表达式 两个判断练习 两个爬取练习 贪婪爬取和非贪…

C++ ——— 引用的概念以及特性

目录 引用的概念 引用在实际代码中的作用 引用的特性 1. 引用在定义时必须初始化 2. 一个变量可以有多个引用 3. 可以给别名再次取别名,或者多次取别名 4. 引用一旦引用了实体,就不能再引用其他实体了 引用的概念 引用不是新定义一个变量&#x…

Linux-异步IO和存储映射IO

异步IO 在 I/O 多路复用中,进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中,当文件描述符上可以执行 I/O 操作时,进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其它的任务…

嵌入式入门Day23

数据结构Day4 操作受限的线性表栈基本概念顺序栈顺序栈结构创建顺序栈判空和判满栈扩容入栈出栈遍历销毁栈 链式栈队列基本概念顺序队列循环顺序队列定义循环队列的创建循环顺序队列的判空和判满循环顺序队列的入队循环顺序队列的遍历循环顺序队列的出队循环顺序队列的销毁 链式…

C语言实验 一维数组

时间:2024.12.3 一、实验 7-1 交换最小值和最大值 #include<stdio.h> int main() {int n, a[10], i, min = 0, max = 0;scanf("%d", &n);for (i = 0; i < n; i++){scanf("%d",&a[i]);}for (i = 0; i < n; i++){if (a[min] > a[i…

聚合支付系统官方个人免签系统三方支付系统稳定安全高并发

系统采用fastadmin框架独立全新开发&#xff0c;安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一-些JD&#xff0c;TB等业务定制&#xff0c;子账号业务逻辑API 非常详细&#xff0c;方便内置对接! 注意&#xff1a;系统没有配置文档很使用教程&#xff0c;不清楚…

HTMLCSS 奇幻森林:小熊的甜蜜蛋糕派对大冒险

这个 HTML 页面包含了一个背景、多个下落的蛋糕图片和一个左右移动的loopy图片,实现了一个小熊吃蛋糕的效果 演示效果 HTML&CSS <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>ideal life</title><style…

电脑关机的趣味小游戏——system函数、strcmp函数、goto语句的使用

文章目录 前言一. system函数1.1 system函数清理屏幕1.2 system函数暂停运行1.3 system函数电脑关机、重启 二、strcmp函数三、goto语句四、电脑关机小游戏4.1. 程序要求4.2. 游戏代码 总结 前言 今天我们写一点稍微有趣的代码&#xff0c;比如写一个小程序使电脑关机&#xf…