JavaScript装饰者模式与AOP:从ES3到ES7的实现演进

作者:纸飞机时间:2026-10-23 08:53:34浏览:400

本文详解JavaScript中装饰者模式与面向切面编程(AOP)的结合应用。通过ES3高阶函数、ES5 Object.defineProperty、原型链及ES7修饰器四种方式实现代码解耦,并展示在性能上报与异常处理中的实际场景,帮助开发者无痛扩展业务逻辑。

装饰者模式与AOP基础概念

装饰者模式的核心概念解析

以照片添加滤镜为例,同一张底片通过不同滤镜组合可呈现多样效果。这正是装饰者模式的直观体现:在不修改原始对象(照片)的前提下,动态为其附加新功能(滤镜)。

鉴于 JavaScript 的动态特性,函数作为一等公民极易被修改。然而,直接改写原有函数会损害代码的可维护性与扩展性,甚至可能污染其他业务逻辑,因此应尽量避免直接篡改。

面向切面编程(AOP)的基本理念

“餐前洗手、饭后漱口”是 AOP 的生活化隐喻。其中,“吃饭”视为切点,而洗手或漱口则是在切点前后插入的额外动作,体现了对核心流程的无侵入式增强。

AOP(Aspect-Oriented Programming,面向切面编程)是对面向对象编程(OOP)的重要补充。它通过将日志上报、异常处理等与业务无关的功能隔离开来,降低了业务逻辑间的耦合度,提升了非业务功能的复用率与开发效率。

在 JavaScript 中,虽然可以利用装饰者模式来达成 AOP 的效果,但二者并非同一维度的概念:AOP 属于编程范式,而装饰者模式属于设计模式。

ES3/ES5/原型链实现方案

ES3 环境下的装饰者实现方案

掌握基本概念后,首先编写一段兼容 ES3 标准的代码来演示装饰者模式的具体实现:


// 原函数
var takePhoto =function(){
console.log('拍照片');
}
// 定义 aop 函数
var after=function( fn, afterfn ){ 
return function(){
let res = fn.apply( this, arguments ); 
afterfn.apply( this, arguments );
return res;
}
}
// 装饰函数
var addFilter=function(){
console.log('加滤镜');
}
// 用装饰函数装饰原函数
takePhoto=after(takePhoto,addFilter);
takePhoto();

通过上述方式,成功将拍照逻辑与滤镜逻辑解耦。若后续需增加自动上传功能,只需通过 aop 函数的 after 方法即可无缝添加,无需改动原有核心代码。

ES5 环境下的装饰者实现方案

ES5 引入了 Object.defineProperty 方法,使得为对象动态添加属性变得更加便捷,以下是基于此特性的实现方式:


let takePhoto = function () {
console.log('拍照片');
}
// 给 takePhoto 添加属性 after
Object.defineProperty(takePhoto, 'after', {
writable: true,
value: function () {
console.log('加滤镜');
},
});
// 给 takePhoto 添加属性 before
Object.defineProperty(takePhoto, 'before', {
writable: true,
value: function () {
console.log('打开相机');
},
});
// 包装方法
let aop = function (fn) {
return function () {
fn.before()
fn()
fn.after()
}
}
takePhoto = aop(takePhoto)
takePhoto()

基于原型链与类的装饰者实现思路

在 JavaScript 中,函数与类均拥有原型对象。利用原型链机制,我们可以方便地进行动态扩展。以下展示了基于原型链的装饰者实现代码:


class Test {
takePhoto() {
console.log('拍照');
}
}
// after AOP
function after(target, action, fn) {
let old = target.prototype[action];
if (old) {
target.prototype[action] = function () {
let self = this;
fn.bind(self);
fn(handle);
}
}
}
// 用 AOP 函数修饰原函数
after(Test, 'takePhoto', () => {
console.log('添加滤镜');
});
let t = new Test();
t.takePhoto();

ES7修饰器语法应用

ES7 修饰器语法实现装饰者

ES7 提案引入了 @decorator 修饰器语法,参考阮一峰的相关文章。修饰器本质上是一个用于修改类行为的函数,目前 Babel 转码器已提供支持。需注意,修饰器仅能用于类或其属性、方法,具体差异可参考 MDN 文档中关于 Object.defineProperty 的说明;此外,TypeScript 的实现机制也有所不同,详见 TypeScript Decorator 文档。

接下来,通过修饰器语法演示如何对类方法进行装饰:


function after(target, key, desc) {
const { value } = desc;
desc.value = function (...args) {
let res = value.apply(this, args);
console.log('加滤镜')
return res;
}
return desc;
}
class Test{
@after
takePhoto(){
console.log('拍照')
}
}
let t = new Test()
t.takePhoto()

可以看出,使用修饰器编写的代码结构简洁且逻辑清晰,极大提升了可读性。

性能上报与异常处理场景

应用场景一:性能数据上报

装饰者模式适用于多种场景,典型应用包括记录异步请求耗时并上报性能数据。以下是相关实现代码:


function report(target, key, desc) {
const { value } = desc;
desc.value = async function (...args) {
let start = Date.now();
let res = await value.apply(this, args);
let millis = Date.now()-start;
// 上报代码
return res;
}
return desc;
}
class Test{
@report
getData(url){
// fetch 代码
}
}
let t = new Test()
t.getData()

使用 @report 修饰后的代码会自动上报请求消耗的时间。这种扩展方式既不影响 report 函数的逻辑,也不会干扰业务代码,实现了真正的解耦。

应用场景二:全局异常处理

我们可以利用装饰者对原有代码进行简单的异常捕获与处理,而无需以侵入式方式修改原始逻辑:


function handleError(target, key, desc) {
const { value } = desc;
desc.value = async function (...args) {
let res;
try{
res = await value.apply(this, args);
}catch(err){
// 异常处理
logger.error(err)
}
return res;
}
return desc;
}
class Test{
@handleError
getData(url){
// fetch 代码
}
}
let t = new Test()
t.getData()

通过上述两个示例可见,修饰器的定义虽简单,但功能强大,能有效简化复杂逻辑的处理。

总结与核心原则

全文总结与回顾

本文依次通过高阶函数、原型链、Object.defineProperty 以及 @Decorator 四种方式实现了装饰者模式。回顾核心要点如下:

  • 装饰者模式非常适合为业务代码附加非业务相关功能(如日志上报),其作用类似于给照片添加滤镜。
  • 装饰者模式非常适合无痛扩展他人代码,这在接手维护旧项目时尤为实用。

部分开发者可能认为装饰者模式与 Vue 的 mixin 机制相似,实际上二者均体现了“开放-封闭原则”与“单一职责原则”。

以上就是本文的全部内容,希望能为各位的学习带来帮助,也欢迎大家支持本站。文中示例代码介绍详细,对学习与工作具有参考价值。

作者最新文章