当前位置: 首页 > news >正文

JavaScript装饰者模式

JavaScript装饰者模式

  • 1 什么是装饰者模式
  • 2 模拟装饰者模式
  • 3 JavaScript的装饰者
  • 4 装饰函数
  • 5 AOP装饰函数
  • 6 示例:数据统计上报

1 什么是装饰者模式

在程序开发中,许多时候都我们并不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰者模式。

装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象,它能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

2 模拟装饰者模式

假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第三级时可以发射原子弹,接下来用代码实现这个过程,首先定义一个飞机类:

var Plane = function () {};
Plane.prototype.fire = function () {console.log("发射普通子弹");
};

接下来增加两个装饰类,分别是导弹和原子弹:

var MissileDecorator = function (plane) {this.plane = plane;
};
MissileDecorator.prototype.fire = function () {this.plane.fire();console.log("发射导弹");
};
var AtomDecorator = function (plane) {this.plane = plane;
};
AtomDecorator.prototype.fire = function () {this.plane.fire();console.log("发射原子弹");
};

导弹类和原子弹类的构造函数都接受参数plane对象,并且保存好这个参数,在它们的fire方法中,除了执行自身的操作之外,还调用plane对象的fire方法。这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象都拥有相同的接口(fire方法),当请求达到链中的某个对象时,这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。

接下来测试一下:

var plane = new Plane();
plane = new MissileDecorator(plane);
plane = new AtomDecorator(plane);
plane.fire();// 发射普通子弹
// 发射导弹
// 发射原子弹

3 JavaScript的装饰者

JavaScript可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式,代码如下:

var plane = {fire: function () {console.log("发射普通子弹");},
};
var missileDecorator = function () {console.log("发射导弹");
};
var atomDecorator = function () {console.log("发射原子弹");
};
var fire1 = plane.fire;
plane.fire = function () {fire1();missileDecorator();
};
var fire2 = plane.fire;
plane.fire = function () {fire2();atomDecorator();
};
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹

4 装饰函数

JavaScript中可以很方便地给某个对象扩展属性和方法,但却很难在不改动某个函数源代码的情况下,给该函数添加一些额外的功能。在代码的运行期间,我们很难切入某个函数的执行环境。要想为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,但这是最差的办法,直接违反了开放封闭原则。

现在需要一个办法,在不改变函数源代码的情况下,能给函数增加功能,通过保存原引用的方式就可以改写某个函数:

var a = function () {alert(1);
};
var _a = a;
a = function () {_a();alert(2);
};
a();

这是实际开发中很常见的一种做法,比如我们想给window绑定onload事件,但是又不确定这个事件是不是已经被其他人绑定过,为了避免覆盖掉之前的window.onload函数中的行为,我们一般都会先保存好原先的window.onload,把它放入新的window.onload里执行:

window.onload = function () {alert(1);
};
var _onload = window.onload || function () {};
window.onload = function () {_onload();alert(2);
};

这种方式存在以下两个问题:

  • 必须维护_onload这个中间变量,虽然看起来并不起眼,但如果函数的装饰链较长,或者需要装饰的函数变多,这些中间变量的数量也会越来越多
  • this被劫持

接下来通过AOP,来提供一种完美的方法给函数动态增加功能。

5 AOP装饰函数

首先给出Function.prototype.before方法和Function.prototype.after方法:

Function.prototype.before = function (beforefn) {var __self = this; // 保存原函数的引用return function () {// 返回包含了原函数和新函数的"代理"函数beforefn.apply(this, arguments); // 执行新函数,且保证 this 不被劫持,新函数接受的参数// 也会被原封不动地传入原函数,新函数在原函数之前执行return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,并且保证 this 不被劫持};
};
Function.prototype.after = function (afterfn) {var __self = this;return function () {var ret = __self.apply(this, arguments);afterfn.apply(this, arguments);return ret;};
};

Function.prototype.before接受一个函数当作参数,这个函数即为新添加的函数,它装载了新添加的功能代码。接下来把当前的this保存起来,这个this指向原函数,保证了this不会被劫持,然后返回一个函数。它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序,让新添加的函数在原函数之前执行(前置装饰),这样就实现了动态装饰的效果。

Function.prototype.after的原理跟Function.prototype.before一模一样,唯一不同的地方在于让新添加的函数在原函数执行之后再执行。

<button id="button"></button>
<script>Function.prototype.before = function (beforefn) {var __self = this;return function () {beforefn.apply(this, arguments);return __self.apply(this, arguments);}}document.getElementById = document.getElementById.before(function () {alert(1);});var button = document.getElementById('button');console.log(button);
</script>

再回到window.onload的例子,用Function.prototype.before来增加新的window.onload事件简单了很多:

window.onload = function () {alert(1);
};
window.onload = (window.onload || function () {}).after(function () {alert(2);}).after(function () {alert(3);}).after(function () {alert(4);});

6 示例:数据统计上报

比如页面中有一个登录button,点击这个button会弹出登录浮层,与此同时要进行数据上报,来统计有多少用户点击了这个登录button:

<button tag="login" id="button">点击打开登录浮层</button>
<script>var showLogin = function () {console.log('打开登录浮层');log(this.getAttribute('tag'));}var log = function (tag) {console.log('上报标签为: ' + tag);// (new Image).src = 'http:// xxx.com/report?tag=' + tag; // 真正的上报代码略}document.getElementById('button').onclick = showLogin;
</script>

我们看到在showLogin函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:

<button tag="login" id="button">点击打开登录浮层</button>
<script>Function.prototype.after = function (afterfn) {var __self = this;return function () {var ret = __self.apply(this, arguments);afterfn.apply(this, arguments);return ret;}};var showLogin = function () {console.log('打开登录浮层');}var log = function () {console.log('上报标签为: ' + this.getAttribute('tag'));}showLogin = showLogin.after(log); // 打开登录浮层之后上报数据document.getElementById('button').onclick = showLogin; 
</script>

相关文章:

JavaScript装饰者模式

JavaScript装饰者模式 1 什么是装饰者模式2 模拟装饰者模式3 JavaScript的装饰者4 装饰函数5 AOP装饰函数6 示例&#xff1a;数据统计上报 1 什么是装饰者模式 在程序开发中&#xff0c;许多时候都我们并不希望某个类天生就非常庞大&#xff0c;一次性包含许多职责。那么我们就…...

C++学习笔记01

01.C概述&#xff08;了解&#xff09; c语言在c语言的基础上添加了面向对象编程和泛型编程的支持。 02.第一个程序helloworld&#xff08;掌握&#xff09; #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;//标准命名空间int main() {//co…...

【UE5】初识MetaHuman 创建虚拟角色

步骤 在UE5工程中启用“Quixel Bridge”插件 打开“Quixel Bridge” 点击“MetaHumans-》MetaHuman Presets UE5” 点击“START MHC” 在弹出的网页中选择一个虚幻引擎版本&#xff0c;然后点击“启动 MetaHuman Creator” 等待一段时间后&#xff0c;在如下页面点击选择一个人…...

物流实时数仓:数仓搭建(DWD)一

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 文章目录 系列文章目录前言一、文件编写1.目录创建2.b…...

MATLAB安装

亲自验证有效&#xff0c;多谢这位网友的分享&#xff1a; https://blog.csdn.net/xiajinbiaolove/article/details/88907232...

C语言——预处理详解(#define用法+注意事项)

#define 语法规定 #define定义标识符 语法: #define name stuff #define例子 #include<stdio.h> #define A 100 #define STR "abc" #define FOR for(;;)int main() {printf("%d\n", A);printf("%s\n", STR);FOR;return 0; } 运行结果…...

Linux(23):Linux 核心编译与管理

编译前的任务&#xff1a;认识核心与取得核心原始码 Linux 其实指的是核心。这个【核心(kernel)】是整个操作系统的最底层&#xff0c;他负责了整个硬件的驱动&#xff0c;以及提供各种系统所需的核心功能&#xff0c;包括防火墙机制、是否支持 LVM 或 Quota 等文件系统等等&a…...

Oracle RAC环境下redo log 文件的扩容

环境&#xff1a; 有一个2节点RAC每一个节点2个logfile group每一个group含2个member每一个member的大小为200M 目标&#xff1a;将每一个member的大小有200M扩充到1G。 先来看下redo log的配置&#xff1a; SQL> select * from v$log;GROUP# THREAD# SEQUENCE# …...

Java入门学习笔记一

一、Java语言环境搭建 1、JAVA语言的跨平台原理 1.1、什么是跨平台性&#xff1f; 跨平台就是说&#xff0c;同一个软件可以在不同的操作系统&#xff08;例如&#xff1a;Windows、Linux、mad&#xff09;上执行&#xff0c;而不需要对软件做任务处理。即通过Java语言编写的…...

分布式块存储 ZBS 的自主研发之旅|元数据管理

重点内容 元数据管理十分重要&#xff0c;犹如整个存储系统的“大黄页”&#xff0c;如果元数据操作出现性能瓶颈&#xff0c;将严重影响存储系统的整体性能。如何提升元数据处理速度与高可用是元数据管理的挑战之一。SmartX 分布式存储 ZBS 采用 Log Replication 的机制&…...

六大设计原则

六大设计原则 1、单一职责原则 一个类或者模块只负责完成一个职责或者功能。 2、开放封闭原则 规定软件中的对象、类、模块和函数对扩展应该是开放的&#xff0c;对于修改应该是封闭的。用抽象定义结构&#xff0c;用具体实现扩展细节。 3、里氏替换原则 如果S是T的子类型…...

dockerfile创建镜像 lNMP+wordpress

dockerfile创建镜像 lNMPwordpress nginx dockernginx mysql dockermysql php dockerphp nginx vim nginx.conf vim Dockerfile docker network create --subnet172.17.0.0/16 --opt "com.docker.network.bridge.name""docker1" mynetwork docker buil…...

深入理解——快速排序

目录 &#x1f4a1;基本思想 &#x1f4a1;基本框架 &#x1f4a1;分割方法 ⭐Hoare版本 ⭐挖坑法 ⭐前后指针法 &#x1f4a1;优化方法 ⭐三数取中法 ⭐小区间内使用插入排序 &#x1f4a1;非递归实现快速排序 &#x1f4a1;性能分析 &#x1f4a1;基本思想 任取待排…...

【代码随想录】算法训练计划50

dp 1、123. 买卖股票的最佳时机 III 题目&#xff1a; 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次购…...

【数据分享】2019-2023年我国区县逐年二手房房价数据(Excel/Shp格式)

房价是一个区域发展程度的重要体现&#xff0c;一个区域的房价越高通常代表这个区域越发达&#xff0c;对于人口的吸引力越大&#xff01;因此&#xff0c;房价数据是我们在各项城市研究中都非常常用的数据&#xff01;之前我们分享了2019—2023年我国区县逐月的二手房房价数据…...

Redis设计与实现之整数集合

目录 一、内存映射数据结构 二、整数集合 1、整数集合的应用 2、数据结构和主要操作 3、intset运行实例 创建新intset 添加新元素到 intset 添加新元素到 intset&#xff08;不需要升级&#xff09; 添加新元素到 intset (需要升级) 4、升级 升级实例 5、关于升级 …...

[Kubernetes]2. k8s集群中部署基于nodejs golang的项目以及Pod、Deployment详解

一. 创建k8s部署的镜像 1.部署nodejs项目 (1).上传nodejs项目到节点node1 (2).压缩nodejs项目 (3).构建nodejsDockerfile 1).创建nodejsDockerfile 具体可参考:[Docker]十.Docker Swarm讲解,在/root下创建nodejsDockerfile,具体代码如下: FROM node #把压缩文件COPY到镜像的…...

讯飞星火大模型api调用

讯飞星火大模型&#xff0c;通过websocket方式通信传递协议要求的报文&#xff0c;然后将流式返回的报文拼接为完整的响应内容&#xff0c;status2时是最后一条消息。因为是websocket方式所以是异步响应的&#xff0c;如果想要同步需要使用CountDownLatch控制下线程等待最后一条…...

TCP与UDP:网络世界中的“顺丰快递”与“广播电台”

随着互联网的普及&#xff0c;我们每天都在与网络打交道。而在这背后&#xff0c;数据的传输离不开TCP和UDP这两种传输协议。它们就像网络世界中的“顺丰快递”和“广播电台”&#xff0c;各自有着不同的工作方式和特点。让我们一起来了解一下它们吧&#xff01; 一、TCP&…...

升级Xcode15,iOS17后问题解决

1、Could not build module ‘WebKit’ 报错 解决方案&#xff1a; 编辑文件 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/WebKit.framework/Headers/WKWebsiteDataStore.h 将里面…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...