跳转到内容

JavaScript装饰器提案

来自代码酷
Admin留言 | 贡献2025年4月30日 (三) 19:07的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)

JavaScript装饰器提案[编辑 | 编辑源代码]

装饰器(Decorators)是JavaScript的一个ECMAScript提案(目前处于Stage 3阶段),它提供了一种优雅的方式来修改或增强类及其成员的行为。装饰器本质上是一个高阶函数,能够在不改变原始代码结构的情况下,通过包装目标元素来扩展其功能。

基本概念[编辑 | 编辑源代码]

装饰器最初受到Python和TypeScript的启发,旨在为JavaScript开发者提供一种声明式的方式来增强类、方法、属性或访问器。装饰器通过@符号前缀来使用,可以应用于:

  • 类声明
  • 类方法
  • 类属性
  • 访问器(getter/setter)
  • 参数(较少使用)

装饰器的核心思想是高阶编程——即函数可以作为参数传递或返回其他函数。装饰器函数接收目标元素的相关信息,并返回修改后的版本或完全替换它。

语法示例[编辑 | 编辑源代码]

以下是一个最简单的装饰器使用示例:

// 定义一个装饰器函数
function log(target, name, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args) {
        console.log(`调用方法 ${name},参数: ${args}`);
        return originalMethod.apply(this, args);
    };
    
    return descriptor;
}

class Calculator {
    @log
    add(a, b) {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(2, 3); // 输出: "调用方法 add,参数: 2,3" 并返回5

装饰器类型[编辑 | 编辑源代码]

JavaScript支持多种装饰器类型,每种类型接收不同的参数:

类装饰器[编辑 | 编辑源代码]

应用于类构造函数,接收目标类作为参数:

function sealed(constructor) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

@sealed
class MyClass {}

方法装饰器[编辑 | 编辑源代码]

最常用的装饰器类型,接收三个参数: 1. 目标类的原型(对于静态方法是构造函数) 2. 方法名 3. 属性描述符

属性装饰器[编辑 | 编辑源代码]

与方法装饰器类似,但没有描述符参数:

function readonly(target, name) {
    Object.defineProperty(target, name, { writable: false });
}

class User {
    @readonly
    name = 'John';
}

访问器装饰器[编辑 | 编辑源代码]

应用于getter/setter方法:

function configurable(value) {
    return function(target, name, descriptor) {
        descriptor.configurable = value;
    };
}

class Person {
    @configurable(false)
    get age() { return 30; }
}

装饰器工厂[编辑 | 编辑源代码]

装饰器工厂是一个返回装饰器函数的函数,允许传递参数来定制装饰器行为:

function log(message) {
    return function(target, name, descriptor) {
        const original = descriptor.value;
        descriptor.value = function(...args) {
            console.log(`${message}: ${name}(${args})`);
            return original.apply(this, args);
        };
        return descriptor;
    };
}

class MathOps {
    @log('执行加法')
    add(a, b) { return a + b; }
}

实际应用场景[编辑 | 编辑源代码]

装饰器在现代JavaScript开发中有多种实用场景:

日志记录[编辑 | 编辑源代码]

如前面的示例所示,装饰器非常适合用于方法调用的日志记录。

性能测量[编辑 | 编辑源代码]

测量方法执行时间:

function measure(target, name, descriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args) {
        const start = performance.now();
        const result = original.apply(this, args);
        console.log(`${name} 执行时间: ${performance.now() - start}ms`);
        return result;
    };
    return descriptor;
}

表单验证[编辑 | 编辑源代码]

验证方法参数:

function validate(...validators) {
    return function(target, name, descriptor) {
        const original = descriptor.value;
        descriptor.value = function(...args) {
            validators.forEach((validator, i) => {
                if (!validator(args[i])) {
                    throw new Error(`参数 ${i} 验证失败`);
                }
            });
            return original.apply(this, args);
        };
        return descriptor;
    };
}

function isNumber(value) { return typeof value === 'number'; }

class MathUtils {
    @validate(isNumber, isNumber)
    sum(a, b) { return a + b; }
}

自动绑定this[编辑 | 编辑源代码]

解决回调函数中的this问题:

function autoBind(target, name, descriptor) {
    const original = descriptor.value;
    return {
        configurable: true,
        get() {
            const boundFn = original.bind(this);
            Object.defineProperty(this, name, {
                value: boundFn,
                configurable: true,
                writable: true
            });
            return boundFn;
        }
    };
}

class Button {
    @autoBind
    handleClick() {
        console.log(this); // 总是指向Button实例
    }
}

装饰器执行顺序[编辑 | 编辑源代码]

当多个装饰器应用于同一目标时,它们的执行顺序遵循特定规则:

graph TD A[装饰器工厂执行] --> B[装饰器函数求值] B --> C[从外到内执行装饰器] C --> D[从下到上执行装饰器]

具体来说: 1. 装饰器工厂按从上到下的顺序执行 2. 装饰器函数按从下到上的顺序执行

例如:

@decorator1()
@decorator2()
class Example {}

执行顺序为: 1. 调用decorator1()工厂函数 2. 调用decorator2()工厂函数 3. 应用decorator2返回的装饰器 4. 应用decorator1返回的装饰器

数学表示[编辑 | 编辑源代码]

装饰器可以看作是一个函数变换:

decorator:(target,name?,descriptor?)descriptor

其中:

  • target是装饰目标
  • name是可选的成员名称
  • descriptor是可选的属性描述符
  • 返回修改后的descriptor

注意事项[编辑 | 编辑源代码]

1. 装饰器目前仍是提案,需要使用Babel等转译工具 2. 装饰器不能用于普通函数(只能用于类成员) 3. 过度使用装饰器可能导致代码难以理解 4. 装饰器在编译时执行,而非运行时

浏览器支持[编辑 | 编辑源代码]

截至2023年,装饰器提案尚未被所有浏览器原生支持。可以使用以下工具链:

  • Babel插件:@babel/plugin-proposal-decorators
  • TypeScript:启用experimentalDecorators选项

总结[编辑 | 编辑源代码]

JavaScript装饰器提案为元编程提供了强大的工具,允许开发者以声明式的方式修改类行为。虽然目前仍在提案阶段,但已被广泛用于TypeScript和现代前端框架中。合理使用装饰器可以显著提高代码的可重用性和可维护性,但应注意避免过度使用导致的复杂性。