For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-function-and-object-literal-element-decorators.md.
  • 简体中文
  • Function and Object Literal Decorators S1

    中文标题:函数和对象字面量元素的装饰器

    提案概览
    提案速览

    该提案扩展了 ECMAScript 装饰器提案,以支持在函数(表达式、声明、箭头函数)和对象字面量元素(方法、getter/setter、属性、自动访问器)上使用装饰器。它旨在提供一致的元编程能力并促进装饰器的重用。关键讨论涉及如何处理被装饰函数声明的提升问题,champion 倾向于不提升的行为。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    用于函数的 ECMAScript 装饰器

    本提案旨在为函数表达式、函数声明和对象字面量元素添加对 装饰器 的支持。

    状态

    阶段: 1
    提案 champion: Ron Buckton (@rbuckton)
    最后提交: 2024年2月8日

    更多信息请参见 TC39 提案流程

    作者

    • Ron Buckton (@rbuckton)

    概述与动机

    ECMAScript [装饰器][] 提案通过使用 @ 前缀表达式标记类、类方法和字段的声明,引入了“装饰”这些元素的能力。这些_装饰器_可以在类定义求值期间执行用户自定义代码,以执行各种元编程任务,如构造函数注册、输入验证、日志记录和追踪、反射、元数据等。然而,装饰器目前仅限于类和类元素,尽管这些声明与 function 表达式和声明、箭头函数以及对象字面量方法等声明具有许多共同特征。

    虽然通过函数调用可以轻松实现函数的“装饰”——例如 dec(function() {}) 而不是 @dec function() {} ——但这样的“装饰器”并不具备类和类元素装饰器所享有的优势。类和类元素装饰器会接收一个包含被装饰元素附加信息的 context 对象,并可以利用该对象来验证装饰器目标(即,“此装饰器仅允许用于字段”)、附加元数据以及执行装饰后的注册。这些装饰器还可以选择省略返回值以避免包装/替换元素。如果我们仅支持类和类元素上的装饰器,那么编写可复用的装饰器将变得更加困难,因为这些装饰器由于缺乏 context 而难以同时应用于 dec(function() {}) 风格的装饰。

    对函数的一等装饰器支持将使得编写可复用的装饰器更加容易,并将同样的元编程灵活性扩展到更多声明,从而使整个装饰器特性在语言中得到更广泛的支持和一致性。

    许多适用于类和类元素装饰器的用例同样适用于函数和对象字面量元素:

    • 日志记录/追踪/审计(例如,@Audit() function login(username, passwordHash) { ... }
    • 授权(例如,@Authorize("Administrators") function createUser() { ... }
    • HTTP/REST API 路由(例如,@Get("/posts/:id") function getUser(id) { ... }
    • 测试/注册(例如,@Test() function userIsCreated() { ... }
    • 元数据(例如,@ReturnType(() => Number) function add(x, y) { ... }
    • 生成器蹦床(例如,@DataFlow() function* extractTransformAndLoad(sources) { ... }

    此外,通过在函数上允许装饰器,我们可以更容易地编写装饰器本身:

    /** 一个装饰器,用于包装另一个装饰器并检查被装饰元素。 */
    function AllowedTargets(kinds) {
      const formatter = new Intl.ListFormat("en", { style: "long", type: "disjunction" });
      return function (outerTarget, outerContext) {
        if (outerTarget.kind !== "function") throw new TypeError("@AllowedTargets is only valid on a function");
        return function (innerTarget, innerContext) {
          if (!kinds.includes(innerContext.kind)) {
            throw new TypeError(`@${outerContext.name} is only valid on a ${formatter.format(kinds)}`);
          }
          return outerTarget.call(this, innerTarget, innerContext);
        };
      };
    }
    
    @AllowedTargets(["class", "function"])
    function ClassOrFunctionDecorator(target, context) { ... }

    为了提高语言中装饰器支持的一致性并支持这些用例,我们提议采用以下能力:

    • 支持在以下位置使用 @decorator 语法:
      • 箭头函数和异步箭头函数,无论是否带括号(例如,@dec () => ...@dec x => ...
      • 函数表达式(包括异步函数和生成器)
      • 函数声明(包括异步函数和生成器)
      • 对象字面量方法(包括异步方法和生成器)
      • 对象字面量 getter 和 setter
      • 对象字面量属性赋值(包括简写赋值)
    • 支持在对象字面量属性赋值(包括简写赋值)上使用 accessor 语法

    先例

    语法

    函数装饰器使用与类和方法装饰器相同的语法,只是它们可以放在_函数表达式_或_函数声明_的前导 functionasyncexport 关键字之前,_箭头函数_的_箭头参数_之前,或_异步箭头函数_的 async 关键字之前:

    // 日志/追踪
    @logged
    function doWork() { ... }
    
    // 实用包装器
    const onpress = @debounce(250) (e) => console.log("button pressed: ", e.pressed);
    
    // 元数据
    @ParamTypes(() => [Number, Number])
    @ReturnType(() => Number)
    function add(x, y) { return x + y; }
    
    // React 函数组件
    @withStyles({
      root: { border: '1px solid black' },
    })
    @React.forwardRef
    function Button(props, forwardedRef) {
      ...
    }

    语义

    函数表达式和箭头函数

    函数表达式和箭头函数上的装饰器在返回给包含表达式之前应用。装饰器有机会通过附加属性或元数据来增强函数,用另一个函数包裹或替换该函数,或添加在装饰应用完成后执行的“初始化器”来使用函数的最终引用进行注册。

    函数声明

    函数声明上的装饰器应具有与函数表达式相同的功能,但有一个重要的注意事项。在 ECMAScript 中,函数声明会“提升”——它们的名称和值在包含的块顶部求值,这允许它们在声明之前被调用:

    const a = foo(); // 这是可以的,因为 `foo` 被“提升”到这一行之上。
    function foo() { return 1; }

    这对函数声明是可接受的,因为“提升”值不涉及任何代码执行。然而,装饰器需要在值可访问之前执行。因此,我们必须以某种方式处理在函数本身可以被引用之前应用装饰器的问题。

    多年来,我们讨论过如何在 TC39 全体会议内外解决这个问题。最终,我们剩下以下五个选项之一:

    1. 引入预_求值_步骤
    2. 动态应用装饰器
    3. 不提升被装饰的函数
    4. 不允许在函数声明上使用装饰器
    5. 仅允许在标记的函数声明上使用装饰器

    ✨ — 提案 champion 偏好的方法。

    选项 1:引入预_求值_步骤

    在这种方法中,函数声明继续被提升。在包含的 Script、Module、FunctionBody 或 Block 中的任何语句被求值之前,我们会首先求值并应用所有装饰器。然而,这有几个固有问题可能使其不可行。

    装饰器本身必须要么也是函数声明,要么从另一个文件导入(文件之间不能有循环导入关系):

    const dec = (target, context) => {};
    
    @dec // 错误,因为 `@dec` 最终在 `const dec` 初始化之前被求值。
    function foo() {}

    此外,装饰器工厂的参数不能引用作用域中任何未从另一个文件导入的变量(同样,导入图中不能有循环)。这将使得装饰器无法引用在同一文件中声明的常量,而这在多个函数使用相同基础数据进行装饰时会很有用:

    const BASE = "/api/users";
    
    @route("PUT", `${BASE}/create`) // 错误,因为 `@route` 求值时 `BASE` 未初始化。
    export function createUser(data) { }
    
    @route("GET", `${BASE}/:id`)
    export function getUser(id) { }

    最后,装饰器求值顺序变得更难推理。对于类和类元素装饰器,装饰器表达式按文档顺序_求值_。如果被装饰的函数声明提升,那么这些装饰器的表达式将不再按文档顺序求值,因为它们也会被提升到块的顶部。

    所有这些情况都与装饰器应用于 class 声明的方式不同,这会导致不一致性,可能使用户困惑。因此,不推荐这种方法。

    选项 2:动态应用装饰器

    在这种方法中,装饰器将在执行到达函数声明时或首次访问函数的绑定时被求值和应用:

    f; // f 被访问,所以 f 的装饰器在这里应用
    
    @dec
    function f() {} // 执行到达 f,但什么也不发生,因为它的装饰器已经应用
    
    @dec
    function g() {} // 执行到达 g,所以 g 的装饰器被应用

    这种方法也有重大的固有问题。与选项 1一样,装饰器表达式的求值不再按文档顺序进行。更糟糕的是,装饰器的求值顺序现在可能是非确定性的,因为不同的代码路径可能以不同的顺序接触函数,具体取决于任何数量的变量条件。

    此外,有一些函数声明在正常代码执行中永远不会遇到是很常见的:

    function factory() {
      return {
        getF() { return f; },
        getG() { return g; }
      };
    
      @dec
      function f() {}
    
      @dec
      function g() {}
    }

    在上面的代码中,由于 return,执行永远不会到达 fg 的声明,并且用户代码可能永远不会在结果上调用 getF()getG(),因此 fg 的装饰器可能永远不会被应用。这是一个重要问题,因为装饰器可用于_注册_目的,例如注册自定义 DOM 元素、测试等,如果存在被装饰的函数声明永远不执行装饰器求值的情况,可能导致整个程序以难以诊断且不可预测的方式失败。因此,这种方法会鼓励用户手动“提升”被装饰的函数声明以避免这些陷阱。

    虽然这种方法与选项 3非常相似,但我们目前不建议采用这种方法,因为需要测试任何变量访问是否是首次访问被装饰函数,这将引入非确定性和运行时复杂性。

    ✨ 选项 3:不提升被装饰的函数

    在这种方法中,被装饰的函数声明不会将其值提升到块的顶部。相反,它们的行为更像初始化为函数表达式的 letvar 声明:

    @dec
    function f() {}
    
    // 本质上等价于
    
    var f = @dec function() {};

    选项 2非常相似,这有被装饰的函数声明需要手动“提升”到使用它们的任何代码之上的注意事项。然而,与选项 2不同,运行时不需要引入次优的首次使用检查。

    应当注意,这种方法在现有代码库转向采用函数声明上的装饰器时引入了潜在的重构风险,但这可以通过 linter 和类型系统部分缓解。

    尽管存在这些注意事项,这种方法的好处是装饰器表达式的求值保持文档顺序,并且与类和类元素装饰器完全一致,这将消除选项 1 和 2 引入的不确定性。

    这是当前提案 champions 偏好的方法,因为它提供最一致的语义。

    选项 4:不允许在函数声明上使用装饰器

    这是迄今为止最简单的选项,因为它完全避免了问题。然而,不允许在函数声明上使用装饰器将与本文提案的其余部分不一致,并且不会赋予函数声明在概述与动机中指出的装饰器好处。因此,提案 champions 不推荐这种方法。

    选项 5:仅允许在标记的函数声明上使用装饰器

    这种方法类似于选项 4,即普通函数声明不能被装饰,但它也是选项 3的变体,其中被装饰的函数声明不会被提升。它不是依靠存在装饰器作为选项 3 提升行为的语法选择,而是要求一个_额外的_语法选择,形式为前缀关键字,例如 varletconst

    function fn() {} // 正常提升,不能被装饰
    
    @dec
    var function fnv() {}
    // 等价于:
    var fnv = @dec function() {};
    
    
    @dec
    let function fnl() {}
    // 等价于:
    let fnl = @dec function() {};
    
    
    @dec
    const function fnc() {}
    // 等价于:
    const fnc = @dec function() {};

    虽然这是一个有趣的方法,但其他未来的提案可能会更好地利用 const function,例如指示函数不执行任何修改,因此我们不情愿推荐这种方法。

    对象字面量元素

    对象字面量元素上的装饰器的行为将很像类和类元素上的装饰器。此外,本提案旨在将 accessor 关键字从类字段扩展到与属性赋值和简写属性赋值一起使用:

    const y = 2;
    const obj = {
      accessor x: 1, // 属性赋值上的自动访问器
      accessor y, // 简写属性赋值上的自动访问器
    };
    
    const desc = Object.getOwnPropertyDescriptor(obj, "x");
    typeof desc.get; // "function"
    typeof desc.set; // "function"

    通过支持 accessor,我们将促进类自动访问器装饰器在对象字面量上的重用。

    装饰器表达式求值

    函数装饰器将按文档顺序求值,与其他装饰器一样。函数装饰器_不能_访问函数体内的局部作用域,因为它们在包含函数的词法环境中求值。

    例如,给定源代码

    @A
    @B
    function foo() {}

    装饰器表达式将按以下顺序_求值_:AB

    装饰器应用顺序

    函数装饰器按相反顺序应用,与语言中其他地方的装饰器求值保持一致。

    例如,给定源代码

    @A
    @B
    function foo() {
    }

    装饰器将按以下顺序_应用_:

    • BAfunction foo()

    元数据

    与类装饰器非常相似,函数装饰器可以定义元数据,然后安装在函数本身:

    const meta = (k, v) => (_, context) => { context.metadata[k] = v; };
    
    @meta("foo", "bar")
    function baz() {}
    
    console.log(baz[Symbol.metadata]["foo"]); // 打印:bar

    目前,提供给类方法装饰器的 context.metadata 属性安装在包含的类上。因此,类方法不能定义改为在方法本身上的元数据。本提案不寻求改变 context.metadata,但可能会选择添加 context.functionMetadatacontext.function.metadata(或类似)属性,以允许在方法上定义元数据。

    对象字面量元素装饰器的行为将类似于类元素装饰器,即 context.metadata 对象将安装在对象字面量本身,以便所有类元素装饰器可以共享相同的元数据对象,促进类方法装饰器的重用。为了与函数装饰器保持一致,我们也可能寻求添加 context.functionMetadata(或类似)属性,用于需要特定于函数的元数据的方法。

    语法(Grammar)

      PropertyDefinition[Yield, Await] :
    -   IdentifierReference[?Yield, ?Await]
    -   CoverInitializedName[?Yield, ?Await]
    -   PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
    -   MethodDefinition[?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? IdentifierReference[?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? CoverInitializedName[?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? `accessor` IdentifierReference[?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? `accessor` PropertyName[?Yield, ?Await] `:` AssignmentExpression[+In, ?Yield, ?Await]
    +   DecoratorList[?Yield, ?Await]? MethodDefinition[?Yield, ?Await]
        `...` AssignmentExpression[+In, ?Yield, ?Await]
      
      # 函数声明和表达式
      FunctionDeclaration[Yield, Await, Default] :
    -   `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
    -   [+Default] `function` `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
    +   DecoratorList[?Yield, ?Await]? `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
    +   [+Default] DecoratorList[?Yield, ?Await]? `function` `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
      
    - FunctionExpression :
    -   `function` BindingIdentifier[~Yield, ~Await]? `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
    + FunctionExpression[Yield, Await] :
    +   DecoratorList[?Yield, ?Await]? `function` BindingIdentifier[~Yield, ~Await]? `(` FormalParameters[~Yield, ~Await] `)` `{` FunctionBody[~Yield, ~Await] `}`
      
      ArrowFunction[In, Yield, Await] :
    -   ArrowParameters[?Yield, ?Await] [no LineTerminator here] `=>` ConciseBody[?In]
    +   DecoratorList[?Yield, ?Await]? ArrowParameters[?Yield, ?Await] [no LineTerminator here] `=>` ConciseBody[?In]
      
      AsyncArrowFunction[In, Yield, Await] :
    -   `async` [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] [no LineTerminator here] `=>` AsyncConciseBody[?In]
    -   CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] [no LineTerminator here] `=>` AsyncConciseBody[?In]
    +   DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] [no LineTerminator here] `=>` AsyncConciseBody[?In]
    +   DecoratorList[?Yield, ?Await]? CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] [no LineTerminator here] `=>` AsyncConciseBody[?In]
      
      GeneratorDeclaration[Yield, Await, Default] :
    -   `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
    -   [+Default] `function` `*` `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
    +   DecoratorList[?Yield, ?Await]? `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
    +   [+Default] DecoratorList[?Yield, ?Await]? `function` `*` `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
      
    - GeneratorExpression :
    -   `function` `*` BindingIdentifier[+Yield, ~Await]? `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
    + GeneratorExpression[Yield, Await] :
    +   DecoratorList[?Yield, ?Await]? `function` `*` BindingIdentifier[+Yield, ~Await]? `(` FormalParameters[+Yield, ~Await] `)` `{` GeneratorBody `}`
      
      AsyncGeneratorDeclaration[Yield, Await, Default] :
    -   `async` [no LineTerminator here] `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
    -   [+Default] `async` [no LineTerminator here] `function` `*` `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
    +   DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
    +   [+Default] DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
      
    - AsyncGeneratorExpression :
    -   `async` [no LineTerminator here] `function` `*` BindingIdentifier[+Yield, +Await]? `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
    + AsyncGeneratorExpression[Yield, Await] :
    +   DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `*` BindingIdentifier[+Yield, +Await]? `(` FormalParameters[+Yield, +Await] `)` `{` AsyncGeneratorBody `}`
      
      AsyncFunctionDeclaration[Yield, Await, Default] :
    -   `async` [no LineTerminator here] `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
    -   [+Default] `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
    +   DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
    +   [+Default] DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
      
    - AsyncFunctionExpression :
    -   `async` [no LineTerminator here] `function` BindingIdentifier[~Yield, +Await]? `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`
    + AsyncFunctionExpression[Yield, Await] :
    +   DecoratorList[?Yield, ?Await]? `async` [no LineTerminator here] `function` BindingIdentifier[~Yield, +Await]? `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}`

    API

    本提案中引入的装饰器的 API 与[装饰器][]提案一致。给定装饰器将被运行时以两个参数调用,targetcontext,其值取决于被装饰的元素。这些装饰器的返回值可能替换被装饰元素的全部或部分。

    函数装饰器的结构

    函数装饰器预期接受两个参数:targetcontext。与类或类方法装饰器非常相似,target 参数将是装饰的函数。

    函数装饰器的 context 将包含有关函数的有用信息:

    type FunctionDecoratorContext = {
      kind: "function";
      name: string | symbol | undefined;
      metadata: object;
      addInitializer(initializer: () => void): void;
    }
    • kind — 指示被装饰元素的种类。
    • name — 函数的名称。由于属性赋值中赋值的名称,名称可能是一个符号。
    • metadata — 与装饰器元数据提案保持一致,您可以向函数附加元数据。
    • addInitializer — 这允许您附加一个在装饰应用之后运行的额外初始化器,就像类方法的装饰器一样。

    从此装饰器返回一个函数将用该函数替换 target,返回 undefined 将使 target 保持不变,返回其他任何内容都是错误。

    对象字面量方法/Getter/Setter 装饰器的结构

    对象字面量方法装饰器的行为与类方法装饰器非常相似,其 target 是被装饰的方法。

    对象字面量方法装饰器的 context 将包含有关方法的有用信息:

    type ObjectLiteralMethodDecoratorContext = {
      kind: "object-method"; // 或者也许只是 "method",因为它们相似
      name: string | symbol;
      private: false;
      static: false;
      metadata: object;
      functionMetadata: object; // (如果我们选择允许函数自身的元数据)
      addInitializer(initializer: () => void): void;
    }
    • kind — 指示被装饰元素的种类。
    • name — 方法的名称。
    • private — 元素是否具有私有名称。目前对于对象字面量方法,这始终为 false
    • static — 元素是否声明为 static。目前对于对象字面量方法,这始终为 false
      • 注意:这有待讨论,因为可能应该是 true,因为没有每实例求值需要考虑。
    • metadata — 与装饰器元数据提案保持一致,您可以向包含此方法的对象附加元数据。
    • functionMetadata — 如果我们选择允许每函数元数据,这将是安装在方法上的唯一对象。
    • addInitializer — 这允许您附加一个在装饰应用之后运行的额外初始化器,就像类方法的装饰器一样。

    getter 和 setter 的装饰器上下文行为类似:

    type ObjectLiteralGetterDecoratorContext = {
      kind: "object-getter"; // 或者也许只是 "getter"?
      ... // 来自 ObjectLiteralMethodDecoratorContext 的其他属性
    }
    
    type ObjectLiteralSetterDecoratorContext = {
      kind: "object-setter"; // 或者也许只是 "setter"?
      ... // 来自 ObjectLiteralMethodDecoratorContext 的其他属性
    }

    从此装饰器返回一个函数将用该函数替换 target,返回 undefined 将使 target 保持不变,返回其他任何内容都是错误。

    对象字面量属性赋值装饰器的结构

    属性赋值(和简写属性赋值)装饰器的行为与类字段装饰器非常相似,其中 target 始终为 undefined

    对象字面量属性赋值装饰器的 context 将包含有关该属性的有用信息:

    type ObjectLiteralPropertyDecoratorContext = {
      kind: "object-property"; // 或者也许只是 "property"
      name: string | symbol;
      private: false;
      static: false;
      metadata: object;
      addInitializer(initializer: () => void): void;
    }
    • kind — 指示被装饰元素的种类。
    • name — 属性的名称。
    • private — 元素是否具有私有名称。目前对于对象字面量属性,这始终为 false
    • static — 元素是否声明为 static。目前对于对象字面量属性,这始终为 false
      • 注意:这有待讨论,因为可能应该是 true,因为没有每实例求值需要考虑。
    • metadata — 与装饰器元数据提案保持一致,您可以向包含此属性的对象附加元数据。
    • addInitializer — 这允许您附加一个在装饰应用之后运行的额外初始化器,就像类字段的装饰器一样。

    从此装饰器返回一个函数将链接一个新的初始化器修改器,就像类字段装饰器一样:

    const addOne = (_target, _context) => x => x + 1;
    
    const obj = {
      @addOne x: 2,
    };
    
    console.log(obj.x); // 3

    返回 undefined 将使初始化器修改器链保持不变,返回其他任何内容都是错误。

    对象字面量自动访问器装饰器的结构

    自动访问器属性赋值(和简写属性赋值)装饰器的行为与类自动访问器装饰器非常相似,其中 target 是一个 { get, set } 对象,指向自动访问器的当前 getter 和 setter。

    对象字面量自动访问器装饰器的 context 将包含有关该属性的有用信息:

    type ObjectLiteralAutoAccessorDecoratorContext = {
      kind: "object-accessor"; // 或者也许只是 "accessor",因为它们相似
      name: string | symbol;
      private: false;
      static: false;
      metadata: object;
      accessorMetadata: { get: object, set: object }; // (如果我们选择允许函数本身的元数据)
      addInitializer(initializer: () => void): void;
    }
    • kind — 指示被装饰元素的种类。
    • name — 元素的名称。
    • private — 元素是否具有私有名称。目前对于对象字面量元素,这始终为 false
    • static — 元素是否声明为 static。目前对于对象字面量元素,这始终为 false
      • 注意:这有待讨论,因为可能应该是 true,因为没有每实例求值需要考虑。
    • metadata — 与装饰器元数据提案保持一致,您可以向包含此元素的对象附加元数据。
    • addInitializer — 这允许您附加一个在装饰应用之后运行的额外初始化器,就像类元素的装饰器一样。

    从此装饰器返回一个 { get, set, init } 对象将允许替换目标的 getter 或 setter,或链接一个新的初始化器修改器,就像类自动访问器装饰器一样:

    const addOne = (_target, _context) => ({ init: x => x + 1 });
    
    const obj = {
      @addOne accessor x: 2,
    };
    
    console.log(obj.x); // 3

    返回 undefined 将使目标的 getter、setter 和初始化器修改器链保持不变。返回 getsetinit 属性中的任何一个为 undefined 将分别使 getter、setter 或初始化器修改器链保持不变。返回其他任何内容都是错误。

    示例

    函数包装器工具

    输入防抖

    function debounce(timeout) {
      return function (target, context) {
        switch (context.kind) {
          case "method":
          case "object-method":
          case "function":
            break;
          default:
            throw new Error(`Not supported for kind: ${context.kind}`);
        }
    
        let timer;
        let deferred;
        function run(thisArg, args) {
          if (timer) {
            clearTimeout(timer);
            timer = undefined;
          }
    
          const { resolve, reject } = deferred;
          deferred = undefined;
          try {
            resolve(target.apply(thisArg, args));
          }
          catch (e) {
            reject(e);
          }
        }
    
        return function (...args) {
          if (timer) {
            clearTimeout(timer);
            timer = undefined;
          }
          deferred ??= Promise.withResolvers();
          timer = setTimeout(() => run(this, args), timeout);
          return deferred.promise;
        };
      }
    }
    
    obj.on("change", @debounce(100) e => { ... });

    操作重试

    function retry({ maxAttempts, shouldRetry }) {
      return function (target, context) {
        switch (context.kind) {
          case "method":
          case "object-method":
          case "function":
            break;
          default:
            throw new Error(`Not supported for kind: ${context.kind}`);
        }
    
        return async function (...args) {
          for (let i = maxAttempts; i > 1; i- ) {
            try {
              return await target.apply(this, args);
            }
            catch (e) {
              if (!shouldRetry || shouldRetry(e)) continue;
              throw e;
            }
          }
          return await target.apply(this, args);
        }
      }
    }
    
    @retry({ maxAttempts: 3, shouldRetry: e => e instanceof IOError })
    export async function downloadFile(url) { ... }

    多用户 Web 应用中的授权

    注意:此示例利用了 AsyncContext 提案。

    const authVar = new AsyncContext.Variable();
    
    function auth(role) {
      return function (target, context) {
        switch (context.kind) {
          case "method":
          case "object-method":
          case "function":
            break;
          default:
            throw new Error(`Not supported for kind: ${context.kind}`);
        }
    
        return function (...args) {
          const user = authVar.get();
          if (!user) throw new Error("Not authenticated");
          if (!user.isInRole(role)) throw new Error("Not authorized");
          return target.apply(this, args);
        };
      };
    }
    
    export function runAs(user, cb) {
        return authVar.run(user, cb);
    }
    
    @auth("Administrator")
    export async function createUser(newUser) { ... }

    在此示例中,多用户 Web 应用将通过调用 runAs 建立当前用户上下文。在回调中,如果调用了 createUser,则检索与当前 AsyncContext.Variable 关联的用户,并在调用函数体本身之前检查访问权限。

    AWS Lambda 上的 Serverless 应用

    这些示例源自 AWS 的 Chalice,一个用于 Amazon Web Services 的 Python 库。这些示例涉及附加元数据和注册等概念。

    Rest API

    import { Chalice } from "chalice";
    const app = new Chalice({ appName: "helloworld" });
    
    @app.route("/")
    function index() { return { "hello": "world" }; }

    计划任务

    import { Chalice, Rate } from "chalice";
    const app = new Chalice({ appName: "helloworld" });
    
    @app.schedule(new Rate(5, { unit: Rate.MINUTES }))
    function periodicTask(event) { ... }
    将 lambda 函数连接到 S3 事件
    import { Chalice, Rate } from "chalice";
    const app = new Chalice({ appName: "helloworld" });
    
    @app.on_s3_event({ bucket: "mybucket" })
    function handler(event) {
      console.log(`Object uploaded for bucket: ${event.bucket}, key: ${event.key}`);
    }

    相关提案

    TODO

    以下是推进 TC39 提案流程 每个阶段所需的高级任务列表:

    阶段 1 入口标准

    • 确定了一个推进的“champion”。
    • 概述问题或需求以及解决方案总体形态的散文
    • 说明性的使用示例
    • 高级 API

    阶段 2 入口标准

    阶段 2.7 入口标准

    阶段 3 入口标准

    • 针对主要使用场景的 Test262 验收测试已经编写并合并

    阶段 4 入口标准

    • 两个兼容的实现通过验收测试:[1], [2]
    • 已向 tc39/ecma262 发送包含集成规范文本的拉取请求
    • ECMAScript 编辑已签署拉取请求