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 示例:数据统计上报 1 什么是装饰者模式 在程序开发中,许多时候都我们并不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就…...
C++学习笔记01
01.C概述(了解) c语言在c语言的基础上添加了面向对象编程和泛型编程的支持。 02.第一个程序helloworld(掌握) #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” 在弹出的网页中选择一个虚幻引擎版本,然后点击“启动 MetaHuman Creator” 等待一段时间后,在如下页面点击选择一个人…...

物流实时数仓:数仓搭建(DWD)一
系列文章目录 物流实时数仓:采集通道搭建 物流实时数仓:数仓搭建 物流实时数仓:数仓搭建(DIM) 物流实时数仓:数仓搭建(DWD)一 文章目录 系列文章目录前言一、文件编写1.目录创建2.b…...
MATLAB安装
亲自验证有效,多谢这位网友的分享: 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 核心编译与管理
编译前的任务:认识核心与取得核心原始码 Linux 其实指的是核心。这个【核心(kernel)】是整个操作系统的最底层,他负责了整个硬件的驱动,以及提供各种系统所需的核心功能,包括防火墙机制、是否支持 LVM 或 Quota 等文件系统等等&a…...

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

Java入门学习笔记一
一、Java语言环境搭建 1、JAVA语言的跨平台原理 1.1、什么是跨平台性? 跨平台就是说,同一个软件可以在不同的操作系统(例如:Windows、Linux、mad)上执行,而不需要对软件做任务处理。即通过Java语言编写的…...

分布式块存储 ZBS 的自主研发之旅|元数据管理
重点内容 元数据管理十分重要,犹如整个存储系统的“大黄页”,如果元数据操作出现性能瓶颈,将严重影响存储系统的整体性能。如何提升元数据处理速度与高可用是元数据管理的挑战之一。SmartX 分布式存储 ZBS 采用 Log Replication 的机制&…...
六大设计原则
六大设计原则 1、单一职责原则 一个类或者模块只负责完成一个职责或者功能。 2、开放封闭原则 规定软件中的对象、类、模块和函数对扩展应该是开放的,对于修改应该是封闭的。用抽象定义结构,用具体实现扩展细节。 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…...

深入理解——快速排序
目录 💡基本思想 💡基本框架 💡分割方法 ⭐Hoare版本 ⭐挖坑法 ⭐前后指针法 💡优化方法 ⭐三数取中法 ⭐小区间内使用插入排序 💡非递归实现快速排序 💡性能分析 💡基本思想 任取待排…...
【代码随想录】算法训练计划50
dp 1、123. 买卖股票的最佳时机 III 题目: 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意:你不能同时参与多笔交易(你必须在再次购…...

【数据分享】2019-2023年我国区县逐年二手房房价数据(Excel/Shp格式)
房价是一个区域发展程度的重要体现,一个区域的房价越高通常代表这个区域越发达,对于人口的吸引力越大!因此,房价数据是我们在各项城市研究中都非常常用的数据!之前我们分享了2019—2023年我国区县逐月的二手房房价数据…...

Redis设计与实现之整数集合
目录 一、内存映射数据结构 二、整数集合 1、整数集合的应用 2、数据结构和主要操作 3、intset运行实例 创建新intset 添加新元素到 intset 添加新元素到 intset(不需要升级) 添加新元素到 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调用
讯飞星火大模型,通过websocket方式通信传递协议要求的报文,然后将流式返回的报文拼接为完整的响应内容,status2时是最后一条消息。因为是websocket方式所以是异步响应的,如果想要同步需要使用CountDownLatch控制下线程等待最后一条…...
TCP与UDP:网络世界中的“顺丰快递”与“广播电台”
随着互联网的普及,我们每天都在与网络打交道。而在这背后,数据的传输离不开TCP和UDP这两种传输协议。它们就像网络世界中的“顺丰快递”和“广播电台”,各自有着不同的工作方式和特点。让我们一起来了解一下它们吧! 一、TCP&…...
升级Xcode15,iOS17后问题解决
1、Could not build module ‘WebKit’ 报错 解决方案: 编辑文件 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/WebKit.framework/Headers/WKWebsiteDataStore.h 将里面…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...