JavaScript装饰器从基础到实战教程

2025-11-19 17:50

本文主要是介绍JavaScript装饰器从基础到实战教程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《JavaScript装饰器从基础到实战教程》装饰器是js中一种声明式语法特性,用于在不修改原始代码的情况下,动态扩展类、方法、属性或参数的行为,本文将从基础概念入手,逐步讲解装饰器的类型、用法、进阶...

装饰器(Decorator)是JavaScript中一种​​声明式​​的语法特性,用于在不修改原始代码的情况下,动态扩展类、方法、属性或参数的行为。它本质上是一个​​函数​​,通过@符号附加到目标上,让代码更优雅、可维护。本文将从基础概念入手,逐步讲解装饰器的类型、用法、进阶技巧及实战场景。

一、装饰器基础概念

1.1 什么是装饰器?

装饰器是一种​​特殊函数​​,接收目标对象(类、方法、属性等)作为参数,返回修改后的目标或新的描述符。其核心价值是​​分离关注点​​——将日志记录、权限验证、性能监控等功能从业务代码中剥离,通过装饰器“装饰”到目标上。

1.2 装饰器的语法

装饰器使用@符号,紧跟装饰器函数名,放置在目标声明前:

@decorator
class MyClass {} // 类装饰器
class MyClass {
  @decorator
  myMethod() {} // 方法装饰器
  @decorator
  myProperty; // 属性装饰器
}

等价于:

class MyClass {}
MyClass = decorator(MyClass) || MyClass; // 类装饰器
MyClass.prototype.myMethod = decorator(MyClass.prototype, 'myMethod', Object.getOwnPropertyDescriptor(MyClass.prototype, 'myMethod')) || MyClass.prototype.myMethod; // 方法装饰器

1.3 装饰器的执行时机

装饰器在​​编译阶段​​执行(ES6模块加载时),而非运行时。例如:

function logClass(target) {
  console.log('类装饰器执行'); // 编译阶段输出
  target.prototype.timestamp = new Date();
}
@logClass
class MyClass {}
// 实例化时不会再次执行装饰器
const instance = new MyClass();
console.log(instance.timestamp); // 输出编译阶段的日期

二、装饰器的主要类型

装饰器可分为五大类,分别作用于不同的目标:

2.1 类装饰器

​作用​​:修改类的构造函数或原型,添加静态属性/方法或实例属性/方法。
​参数​​:类的构造函数(target)。
​示例​​:为类添加静态属性和实例属性。

function addStaticProperty(staticProp, value) {
  return function(target) {
    target[staticProp] = value; // 添加静态属性
  };
}
function addInstanceProperty(instanceProp, initialValue) {
  return function(target) {
    target.prototype[instanceProp] = initialValue; // 添加实例属性
  };
}
@addStaticProperty('version', '1.0.0')
@addInstanceProperty('timestamp', new Date())
class MyClass {}
console.log(MyClass.version); // 输出: 1.0.0
const instance = new MyClass();
console.log(instance.timestamp); // 输出: 编译阶段的日期

2.2 方法装饰器

​作用​​:修改方法的描述符(valuewritable等),实现日志、权限、性能监控等功能。
​参数​​:

  • target:静态方法为类的构造函数,实例方法为类的原型;
  • propertyKey:方法名;
  • descriptor:方法描述符(包含vajsluewritable等属性)。
    ​示例​​:记录方法调用日志。
function logMethod(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`调用方法 ${propertyKey},参数: ${jsON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`方法 ${propertyKey} 返回: ${result}`);
    return result;
  };
  return descriptor;
}
class Calculator {
  @logMethod
  add(a, b) {
    return a + b;
  }
}
const calc = new Calculator();
calc.add(2, 3); 
// 输出: 
// 调用方法 add,参数: [2,3]
// 方法 add 返回: 5

2.3 属性装饰器

​作用​​:修改属性的描述符(如writable设为false实现只读),或通过Object.defineProperty定义属性的getter/setter。
​参数​​:

  • target:静态属性为类的构造函数,实例属性为类的原型;
  • propertyKey:属性名。
    ​示例​​:实现属性只读。
function readOnly(target, propertyKey) {
  Object.defineProperty(target, propertyKey, {
    writable: false, // 设为不可写
    configurable: false // 设为不可配置(防止后续修改)
  });
}
class Person {
  @readOnly
  name = 'John';
}
const person = new Person();
person.name = 'Jane'; // 严格模式下报错:Cannot assign to read only property 'name'
console.log(person.name); // 输出: John

2.4 访问器装饰器

​作用​​:修改属性的getter或setter方法,实现数据格式化、验证等功能。
​参数​​:与方法装饰器相同(targetpropertyKeydescriptor)。
​示例​​:将属性值转为大写。

function capitalize(target, propertyKey, descriptor) {
  const originalGetter = descriptor.get;
  descriptor.get = function() {
    return originalGetter.call(this).toUpperCase(); // 转为大写
  };
  return descriptor;
}
class User {
  constructor(name) {
    this._name = name;
  }
  @capitalize
  get name() {
    return this._name;
  }
}
const user = new User('john');
console.log(user.name); // 输出: JOHN

2.5 参数装饰器

​作用​​:为方法参数添加元数据(如参数名、验证规则),常用于框架中的依赖注入或参数校验。
​参数​​:

  • target:类的原型(实例方法)或构造函数(静态方法);
  • propertyKey:方法名;
  • parameterIndex:参数的索引(从0开始)。
    ​示例​​:记录参数索引。
function www.chinasem.cnlogParameter(target, propertyKey, parameterIndex) {
  const existingParameters = target[propertyKey + 'Parameters'] || [];
  existingParameters.push(parameterIndex);
  target[propertyKey + 'Parameters'] = existingParameters;
}
China编程class UserService {
  getUser(@logParameter id, @logParameter name) {
    return { id, name };
  }
}
const userService = new UserService();
userService.getUser(1, 'John');
console.log(UserService.prototype.getUserParameters); // 输出: [0, 1]

三、装饰器的高级用法

3.1 装饰器工厂:传递参数

装饰器本身是静态的,若需动态配置,可通过​​装饰器工厂​​(返回装饰器函数的函数)实现。
​示例​​:带参数的权限装饰器。

function checkPermission(requiredRole) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args) {
      const userRole = this.userRole; // 假设实例上有userRole属性
      if (userRole === requiredRole) {
        return originalMethod.apply(this, args);
      } else {
        throw new Error(`无权限:需要${requiredRole}角色`);
      }
    };
    return descriptor;
  };
}
class AdminPanel {
  userRole = 'admin';
  @checkPermission('admin')
  deleteUser() {
    console.log('用户已删除');
  }
}
const panel = new AdminPanel();
panel.deleteUser(); // 输出: 用户已删除
const userPanel = new AdminPanel();
userPanel.userRole = 'user';
userPanel.deleteUser(); // 报错: 无权限:需要admin角色

3.2 多个装饰器的执行顺序

多个装饰器按​​从外到内​​的顺序应用(声明顺序),但​​从内到外​​执行(执行顺序)。
​示例​​:

function dec1(target, propertyKey, descriptor) {
  console.log('装饰器1应用');
  return descriptor;
}
function dec2(target, propertyKey, descriptor) {
  console.log('装饰器2应用');
  return descriptor;
}
class Example {
  @dec1
  @dec2
  method() {}
}
// 输出:
// 装饰器2应用
// 装饰器1应用

3.3 组合装饰器:简化重复代码

通过高阶函数组合多个装饰器,减少重复代码。
​示例​​:组合日志和性能监控装饰器。

function composeDecorators(...decorators) {
  return function(target, propertyKey, descriptor) {
    return decorators.reduceRight((desc, decorator) => decorator(target, propertyKey, desc), descriptor);
  };
}
function logMethod(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`调用方法 ${propertyKey}`);
    return originalMethod.apply(this, args);
  };
  return descriptor;
}
function measureTime(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    const start = Date.now();
    const result = originalMethod.apply(this, args);
    console.log(`方法 ${propertyKey} 执行时间: ${Date.now() - start}ms`);
    return result;
  };
  return descriptor;
}
const logAndMeasure = composeDecorators(logMethod, measureTime);
class DataProcessor {
  @logAndMeasure
  processData(data) {
    // 模拟耗时操作
    for (let i = 0; i < 1e6; i++) {}
    return data;
  }
}
const processor = new DataProcessor();
processor.processData([1, 2, 3]);
// 输出:
// 调用方法 processData
// 方法 processData 执行时间: 5ms

四、装饰器的实战场景

4.1 日志记录

通过装饰器自动记录方法的调用信息和返回值,无需手动添加console.log

function logMethod(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`[LOG] 调用 ${propertyKey},参数: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`[LOG] ${propertyKey} 返回: ${result}`);
    return result;
  };
  return descriptor;
}
class LoggerExample {
  @logMethod
  greet(name) {
    return `Hello, ${name}!`;
  }
}
const logger = new LoggerExample();
logger.greet('Alice');
// 输出:
// [LOG] 调用 greet,参数: ["Alice"]
// [LOG] greet 返回: Hello, Alice!

4.2 权限验证

通过装饰器在方法执行前检查用户权限,避免重复编写权限逻辑。

function hASPermission(requiredPermission) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args) {
      const userPermissions = this.permissions || []; // 假设实例上有permissions属性
      if (userPermissions.includes(requiredPermission)) {
        return originalMethod.apply(this, args);
      } else {
        throw new Error(`无权限:需要${requiredPermission}`);
      }
    };
    return descriptor;
  };
}
class DocumentManager {
  permissions = ['read'];
  @hasPermission('write')
  editDocument(content) {
    console.log('文档已编辑');
  }
}
const manager = new DocumentManager();
manager.editDocument('新内容'); // 报错: 无权限:需要write
manager.permissions.push('write');
manager.editDocument('新内容'); // 输出: 文档已编辑

4.3 性能监控

通过装饰器监控方法的执行时间,快速定位性能瓶颈。

function measurePerformance(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = perfjavascriptormance.now();
    console.log(`[PERF] 方法 ${propertyKey} 执行时间: ${end - start}ms`);
    return result;
  };
  return descriptor;
}
class HeavyCalculation {
  @measurePerformance
  calculateFactorial(n) {
    return n <= 1 ? 1 : n * this.calculateFactorial(n - 1);
  }
}
const caandroidlculator = new HeavyCalculation();
calculator.calculateFactorial(10); // 输出: [PERF] 方法 calculateFactorial 执行时间: 1ms

4.4 自动绑定this

通过装饰器自动绑定方法的this,避免在回调中丢失this

function autobind(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args) {
    return originalMethod.apply(this, args);
  };
  return descriptor;
}
class Button {
  constructor(text) {
    this.text = text;
    this.handleClick = this.handleClick.bind(this); // 传统方式
  }
  @autobind
  handleClick() {
    console.log(`按钮文本: ${this.text}`);
  }
}
const button = new Button('Click Me');
const clickHandler = button.handleClick;
clickHandler(); // 输出: 按钮文本: Click Me(无需手动绑定)

五、注意事项

  1. ​装饰器的执行顺序​​:多个装饰器按从外到内应用,从内到外执行。例如@A @B等同于A(B(target)),但执行顺序是B先于A
  2. ​不能装饰函数​​:函数存在变量提升,装饰器可能在函数声明前执行,导致意外结果。建议使用类或箭头函数。
  3. ​兼容性问题​​:装饰器是ES提案,需通过Babel(@babel/plugin-proposal-decorators)或TypeScript(experimentalDecorators)转译。
  4. ​保持单一职责​​:每个装饰器只做一件事(如日志、权限),避免复杂逻辑,提高可维护性。

六、总结

装饰器是javascript中强大的语法糖,通过声明式的方式扩展类、方法、属性的功能,让代码更清晰、可维护。本文从基础概念讲起,覆盖了类、方法、属性、访问器、参数五大类装饰器,以及装饰器工厂、组合装饰器等高级用法,最后结合实战场景演示了日志、权限、性能监控等常见应用。掌握装饰器,能让你写出更优雅、更模块化的代码。

到此这篇关于JavaScript装饰器从基础到实战教程的文章就介绍到这了,更多相关JavaScript装饰器内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于JavaScript装饰器从基础到实战教程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156211

相关文章

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加

Java使用Spire.Doc for Java实现Word自动化插入图片

《Java使用Spire.DocforJava实现Word自动化插入图片》在日常工作中,Word文档是不可或缺的工具,而图片作为信息传达的重要载体,其在文档中的插入与布局显得尤为关键,下面我们就来... 目录1. Spire.Doc for Java库介绍与安装2. 使用特定的环绕方式插入图片3. 在指定位

springboot的controller中如何获取applicatim.yml的配置值

《springboot的controller中如何获取applicatim.yml的配置值》本文介绍了在SpringBoot的Controller中获取application.yml配置值的四种方式,... 目录1. 使用@Value注解(最常用)application.yml 配置Controller 中