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-alias-accessors.md.
  • 简体中文
  • Alias Accessors S1

    中文标题:别名访问器

    提案概览
    提案速览

    该提案引入了“别名访问器”语法,以减少将类属性转发到另一属性链时的样板代码,解决了访问器通常是累加性而非任意逻辑这一常见情况。它建议使用 alias foo to #foo.value 这样的语法作为显式 getter/setter 对的语法糖,并探讨了聚合、只读别名以及与分组和自动访问器集成的变体。

    Note

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

    别名访问器

    状态

    阶段:1 (截至 2026 年 1 月全体会议)

    作者/提案人:Lea Verou (@leaverou)

    历史记录请参见原始提案

    目录

    1. 状态
    2. 动机
      1. 为什么不用装饰器?
    3. 设计
      1. 聚合
      2. 只读别名
      3. 与分组和自动访问器的集成

    动机

    绝大多数(可能超过 90%,但很难证明)访问器用例是累加性的。 它们的概念模型并非完全是任意的逻辑, 而是对常规属性(公有或私有)之上的一层叠加的转换、副作用和访问控制。

    在某些情况下,该属性是内部实现细节,且从不被单独访问。 这些情况可以通过自动访问器以及内置装饰器很好地处理。

    然而,当使用预先存在的属性(通常是深层嵌套的)作为后备存储时,作者需要退回到通常的样板代码。

    class C {
    	#foo = new Signal(1);
    	get foo () {
    		return this.#foo.value;
    	}
    	set foo (value) {
    		this.#foo.value = value;
    	}
    }

    本提案探索对于这些用例具有更高信噪比的潜在语法,例如:

    class C {
    	#foo = new Signal(1);
    	alias foo to #foo.value;
    }

    其中一些(非互斥的)高层用例包括:

    • 封装:隐藏数据源,以便将来可以更改
    • 人体工程学:缩短频繁访问的属性链
    • 组合:选择性地暴露其他对象或一级协议的 API 表面
    • 访问控制:带有公共 getter 的私有属性

    轶事证据:

    我查看我的访问器用法,它们是(数字未经测量):

    • 75% “属性转发”
    • 15% 惰性初始计算
    • 10% 验证
    • 5% 其他

    — Nicolò Ribaudo (@nicolo-ribaudo)

    为什么不用装饰器?

    私有字段作为目标是一个非常重要的用例,目前无法将它们与定义它们的对象分开传递。

    但即使是公有字段,装饰器也相当尴尬:

    class C {
    	// 但我们不是将其设置为数组!
    	@alias accessor foo = ["a", "b", "c"];
    }
    class C {
    	// 更好,但仍然令人困惑
    	@alias(["a", "b", "c"]) accessor foo;
    }

    也许通过类似引用声明这样的机制可能会变得可以接受:

    class C {
    	@alias accessor foo = x => ref x.a.#b.c;
    }

    class C {
    	@alias(x => ref x.a.#b.c) accessor foo;
    }

    但这仍然远非理想。

    设计

    就像自动访问器一样,如果不需要副作用或逻辑,代理另一属性应只涉及属性名称和对保存底层值的属性(链)的引用的少量额外语法。 它甚至可能是一种单独的值支持的访问器类型,接受属性引用而不是初始值,例如:

    class C {
    	somekeyword foo someseparator #foo.value;
    }

    这将是以下代码的语法糖:

    class C {
    	get foo () {
    		return this.#foo.value;
    	}
    	set foo (value) {
    		this.#foo.value = value;
    	}
    }

    因此,需要讨论的语法是:

    1. 前置定义的关键字。想法:
      • alias
      • delegate
      • forward
      • derived
      • bind 可能与 bind 方法混淆
      • proxy:可能与 Proxy 构造函数混淆
    2. 属性名称与被代理属性链之间的分隔符。想法:
      • =:可能与赋值运算符混淆
      • =>:可能与箭头函数运算符混淆,但也表示绑定
      • via
      • from
      • through
      • to

    为具体起见,本文档其余部分将使用 alias/to

    这些属性链基本上是链的 [ . LiteralPropertyName ] | ComputedPropertyName ]。 开头的 this. 是隐含的。

    然而,它们不是 Reference:概念上,整个链在每次访问时都会被重新求值。

    聚合

    有许多情况需要多个属性通过另一个对象转发。 例如,在使用委托/转发模式(如 ElementInternals)时,需要暴露多个属性。

    当前语法潜在新语法
    class MyElement extends HTMLElement {
    	#internals = this.attachInternals();
    	static formAssociated = true;
    
    	// 暴露
    	get form () {
    		return this.#internals.form;
    	}
    	get labels () {
    		return this.#internals.labels;
    	}
    	get checkValidity () {
    		return this.#internals.checkValidity;
    	}
    	get reportValidity () {
    		return this.#internals.reportValidity;
    	}
    	get willValidate () {
    		return this.#internals.willValidate;
    	}
    	get validationMessage () {
    		return this.#internals.validationMessage;
    	}
    	// ...
    }
    class MyElement extends HTMLElement {
    	#internals = this.attachInternals();
    	static formAssociated = true;
    
    	// 暴露
    	alias form              = #internals.form;
    	alias labels            = #internals.labels;
    	alias checkValidity     = #internals.checkValidity;
    	alias reportValidity    = #internals.reportValidity;
    	alias willValidate      = #internals.willValidate;
    	alias validationMessage = #internals.validationMessage;
    	// ...
    }

    或实现一级协议时:

    当前语法潜在新语法
    class C implements Iterable {
    	get forEach () {
    		return this[Iterable.forEach];
    	}
    	get map () {
    		return this[Iterable.map];
    	}
    	get flatMap () {
    		return this[Iterable.flatMap];
    	}
    	get filter () {
    		return this[Iterable.filter];
    	}
    	get reduce () {
    		return this[Iterable.reduce];
    	}
    	get toArray () {
    		return this[Iterable.toArray];
    	}
    	get some () {
    		return this[Iterable.some];
    	}
    	get every () {
    		return this[Iterable.every];
    	}
    	get find () {
    		return this[Iterable.find];
    	}
    	static get from () {
    		return this[Iterable.from];
    	}
    	// ...
    }
    class C implements Iterable {
    	alias forEach = [Iterable.forEach];
    	alias map     = [Iterable.map];
    	alias flatMap = [Iterable.flatMap];
    	alias filter  = [Iterable.filter];
    	alias reduce  = [Iterable.reduce];
    	alias toArray = [Iterable.toArray];
    	alias some    = [Iterable.some];
    	alias every   = [Iterable.every];
    	alias find    = [Iterable.find];
    	alias from    = [Iterable.from];
    	// ...
    }

    虽然 alias 已经大大改进,但仍然有点重复。 在第二种情况下没有太多可做的,但当名称相同时,也许可以有一个受解构启发的快捷方式:

    class C {
    	alias {
    		form,
    		labels,
    		checkValidity,
    		reportValidity,
    		willValidate,
    		validationMessage
    	} from #internals;
    }

    只读别名

    虽然默认情况下会添加 setter 和 getter,但可以使用可组合 setter 使其只读,可以静默拒绝(DOM 风格)或大声拒绝(JS 风格):

    class C {
    	#foo = 1;
    	@validate(v => false)
    	alias #foo to foo;
    }

    但请注意,只有被代理的属性是只读的才需要这样,否则错误会自然传播:

    class MyElement extends HTMLElement {
    	#internals = this.attachInternals();
    
    	// ElementInternals.prototype.form 是只读的
    	// 所以设置 form 会抛出错误
    	alias form = #internals.form;
    }

    在此情况下,设置 myElement.form 无论如何都会产生错误,因为 ElementInternals.prototype.form 是只读的。 因此,除非我们希望吞掉写入,否则无需显式阻止写入。

    与分组和自动访问器的集成

    该提案的另一个方向可能是成为分组和自动访问器的语法扩展。 自定义后备存储已在#14中被请求。

    该问题中提出的语法是 accessor(#x) x = 1;。 像 accessor x : #x = 1 这样的语法很可能与 TS 冲突。