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-decorator-metadata.md.
  • 简体中文
  • Decorator Metadata S2.7

    中文标题:装饰器元数据

    提案概览
    提案速览

    该提案扩展了装饰器提案,通过添加元数据支持,允许装饰器将元数据与装饰的值关联起来。它引入了通过装饰器的上下文传递的元数据对象,并且在类定义后,该对象作为类的 Symbol.metadata 暴露。

    Note

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

    装饰器元数据

    阶段:3

    规范文本https://github.com/pzuraq/ecma262/pull/10

    本提案旨在扩展装饰器提案,增加装饰器将元数据与被装饰的值关联起来的能力。

    概述

    装饰器是允许用户通过包装和替换现有值来进行元编程的函数。这使它们能够很好地解决许多用例,例如记忆化、响应式、方法绑定等。然而,有许多用例需要装饰器和被装饰类外部的代码能够内省并理解应用了哪些装饰,包括:

    • 验证
    • 序列化
    • Web 组件定义
    • 依赖注入
    • 声明式路由/应用程序结构
    • ...以及更多

    在装饰器提案的早期迭代中,所有装饰器都可以访问类原型,从而允许它们通过使用类作为键直接通过 WeakMap 关联元数据。然而,在最新版本中这不再可能,因为装饰器只能访问它们直接装饰的值(例如,方法装饰器可以访问方法,字段装饰器可以访问字段等)。

    本提案通过提供元数据对象来扩展装饰器,该对象可用于直接存储元数据,或用作 WeakMap 的键。该对象通过装饰器的 context 参数提供,并且在装饰后可通过类定义上的 Symbol.metadata 属性访问。

    详细设计

    整体装饰器签名将更新为以下形式:

    type Decorator = (value: Input, context: {
      kind: string;
      name: string | symbol;
      access: {
        get?(): unknown;
        set?(value: unknown): void;
      };
      isPrivate?: boolean;
      isStatic?: boolean;
      addInitializer?(initializer: () => void): void;
    + metadata?: Record<string | number | symbol, unknown>;
    }) => Output | void;

    新的 metadata 属性是一个普通的 JavaScript 对象。同一个对象会被传递给应用于某个类或其任何元素的所有装饰器。在类被完全定义后,该对象会被赋值给类的 Symbol.metadata 属性。

    一个示例用法可能如下:

    function meta(key, value) {
      return (_, context) => {
        context.metadata[key] = value;
      };
    }
    
    @meta('a', 'x')
    class C {
      @meta('b', 'y')
      m() {}
    }
    
    C[Symbol.metadata].a; // 'x'
    C[Symbol.metadata].b; // 'y'

    继承

    如果被装饰的类有父类,那么 metadata 对象的原型会被设置为父类的元数据对象。这允许元数据以自然的方式被继承,默认情况下利用遮蔽机制,镜像类继承。例如:

    function meta(key, value) {
      return (_, context) => {
        context.metadata[key] = value;
      };
    }
    
    @meta('a', 'x')
    class C {
      @meta('b', 'y')
      m() {}
    }
    
    C[Symbol.metadata].a; // 'x'
    C[Symbol.metadata].b; // 'y'
    
    class D extends C {
      @meta('b', 'z')
      m() {}
    }
    
    D[Symbol.metadata].a; // 'x'
    D[Symbol.metadata].b; // 'z'

    此外,在装饰过程中可以读取父级的元数据,因此子级可以修改或扩展它,而不是覆盖它。

    function appendMeta(key, value) {
      return (_, context) => {
        // 注意:必须复制,不能修改
        const existing = context.metadata[key] ?? [];
        context.metadata[key] = [...existing, value];
      };
    }
    
    @appendMeta('a', 'x')
    class C {}
    
    @appendMeta('a', 'z')
    class D extends C {}
    
    C[Symbol.metadata].a; // ['x']
    D[Symbol.metadata].a; // ['x', 'z']

    私有元数据

    除了直接放在元数据对象上的公共元数据之外,如果装饰器作者不想共享其元数据,还可以将该对象用作 WeakMap 中的键。

    const PRIVATE_METADATA = new WeakMap();
    
    function meta(key, value) {
      return (_, context) => {
        let metadata = PRIVATE_METADATA.get(context.metadata);
    
        if (!metadata) {
          metadata = {};
          PRIVATE_METADATA.set(context.metadata, metadata);
        }
    
        metadata[key] = value;
      };
    }
    
    @meta('a', 'x')
    class C {
      @meta('b', 'y')
      m() {}
    }
    
    PRIVATE_METADATA.get(C[Symbol.metadata]).a; // 'x'
    PRIVATE_METADATA.get(C[Symbol.metadata]).b; // 'y'