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-object-property-count.md.
  • 简体中文
  • Object.propertyCount S1

    提案概览
    提案速览

    该提案在 Object 上引入了三个新的静态方法(keysLength、getOwnPropertyNamesLength、getOwnPropertySymbolsLength),用于在不分配中间数组的情况下计算自身属性的数量,解决了如 Object.keys(obj).length 等常见模式带来的性能问题。这些方法镜像了现有 API(keys、getOwnPropertyNames、getOwnPropertySymbols)的语义,但直接返回计数。

    Note

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

    ECMAScript 提案:高性能对象属性计数

    状态

    提案倡导者:Ruben Bridgewater, Jordan Harband

    作者:Ruben Bridgewater ruben@bridgewater.de, Jordan Harband ljharb@gmail.com

    阶段:1

    概述

    本提案引入了三个内置方法,它们能够高效地获取对象自身属性的数量,而无需承担中间数组分配的性能开销:

    • Object.keysLength(target)——计算自身可枚举的字符串键属性(包括数组索引)的数量,与 Object.keys 对应。
    • Object.getOwnPropertyNamesLength(target)——计算所有自身字符串键属性的数量,与 Object.getOwnPropertyNames 对应。
    • Object.getOwnPropertySymbolsLength(target)——计算所有自身符号键属性的数量,与 Object.getOwnPropertySymbols 对应。

    为了便于审查和推进,该规范被拆分为两个可独立推进的文档。其中一个用于 Object.keysLength,另一个用于其他两个方法的合并。

    动机

    开发者经常依赖这样的模式:

    const obj = { a: 1, b: 2 };
    const count = Object.keys(obj).length;

    然而,这种方法会造成不必要的内存开销和垃圾回收压力,因为仅仅为了计数属性就分配了一个中间数组。高频使用的运行时、框架和库(例如 Node.js、React、Lodash、Angular、Storybook、Excalidraw、VS Code、Svelte、Next.js、three.js、Puppeteer、Tailwind 等)经常使用 Object.keys(obj).length,这加剧了跨应用的整体性能问题。

    例如,React 经常对 props 或 state 键进行计数:

    // React 组件示例
    const propCount = Object.keys(this.props).length;

    用原生且优化的计数方法替换这些模式,能显著减少内存开销、垃圾回收以及由此产生的运行时性能影响。

    具体使用示例

    问题陈述

    目前,准确计算对象属性数量涉及冗长且低效的变通方法:

    const count = [
      ...Object.getOwnPropertyNames(obj),
      ...Object.getOwnPropertySymbols(obj)
    ].length;
    
    const reflectCount = Reflect.ownKeys(obj).length;
    
    assert.strictEqual(count, reflectCount);

    这会创建中间数组,导致不必要的内存使用和垃圾回收,影响应用性能——尤其是在大规模和性能关键的代码路径中。

    这些 API 消除了常见计数模式中的中间数组,并与现有对应方法的语义直接对齐。

    分解计划:三个独立的 API

    为了促进增量式、可独立批准的进展,并与现有且易于理解的 API 紧密对齐,本提案将重写为三个聚焦的方法,它们是现有模式的即插即用、非分配等价物:

    • Object.keysLength(target)Object.keys(target).length(无中间数组)
    • Object.getOwnPropertyNamesLength(target)Object.getOwnPropertyNames(target).length(无中间数组)
    • Object.getOwnPropertySymbolsLength(target)Object.getOwnPropertySymbols(target).length(无中间数组)

    每个 API 在下面单独的章节中使用当前结构(概述、提议 API、语义、示例、Polyfill)进行说明,以便它们能够独立推进。

    Object.keysLength

    概述

    Object.keysLength 返回对象自身可枚举字符串键属性(包括数组索引属性)的数量,而不分配 Object.keys 产生的数组。

    提议 API

    Object.keysLength(target)

    明确的语义

    • 如果 target 不是对象,则抛出 TypeError
    • 仅计算自身属性。
    • 仅计算可枚举属性。
    • 仅计算字符串键属性(包括数组索引)。
    • 不分配任何中间数组。

    示例

    Object.keysLength({}); // 0
    Object.keysLength({ a: 1, b: 2 }); // 2
    Object.keysLength(Object.create({ a: 1 })); // 0(仅自身)
    Object.keysLength(Object.defineProperty({}, 'x', { value: 1, enumerable: false })); // 0

    Polyfill(仅供参考)

    Object.keysLength = function keysLength(target) {
      if (typeof target !== 'object' || target === null) {
        throw new TypeError('Expected an object');
      }
      return Object.keys(target).length;
    };

    Object.getOwnPropertyNamesLength

    概述

    Object.getOwnPropertyNamesLength 返回对象自身字符串键属性的数量,无论其可枚举性如何,而不分配 Object.getOwnPropertyNames 产生的数组。

    提议 API

    Object.getOwnPropertyNamesLength(target)

    明确的语义

    • 如果 target 不是对象,则抛出 TypeError
    • 仅计算自身属性。
    • 仅计算字符串键属性(包括数组索引)。
    • 同时计算可枚举和不可枚举的属性。
    • 不分配任何中间数组。

    示例

    Object.getOwnPropertyNamesLength({}); // 0
    Object.getOwnPropertyNamesLength({ a: 1, b: 2 }); // 2
    const o = Object.defineProperty({}, 'x', { value: 1, enumerable: false });
    Object.getOwnPropertyNamesLength(o); // 1

    Polyfill(仅供参考)

    Object.getOwnPropertyNamesLength = function getOwnPropertyNamesLength(target) {
      if (typeof target !== 'object' || target === null) {
        throw new TypeError('Expected an object');
      }
      return Object.getOwnPropertyNames(target).length;
    };

    Object.getOwnPropertySymbolsLength

    概述

    Object.getOwnPropertySymbolsLength 返回对象自身符号键属性的数量,而不分配 Object.getOwnPropertySymbols 产生的数组。

    提议 API

    Object.getOwnPropertySymbolsLength(target)

    明确的语义

    • 如果 target 不是对象,则抛出 TypeError
    • 仅计算自身属性。
    • 仅计算符号键属性。
    • 无论属性是否可枚举都计算(与 Object.getOwnPropertySymbols 一致)。
    • 不分配任何中间数组。

    示例

    Object.getOwnPropertySymbolsLength({}); // 0
    const s = Symbol('s');
    const o = { [s]: 1 };
    Object.getOwnPropertySymbolsLength(o); // 1

    Polyfill(仅供参考)

    Object.getOwnPropertySymbolsLength = function getOwnPropertySymbolsLength(target) {
      if (typeof target !== 'object' || target === null) {
        throw new TypeError('Expected an object');
      }
      return Object.getOwnPropertySymbols(target).length;
    };

    详细示例和边界情况

    • 空对象
    Object.keysLength({}); // 0
    Object.getOwnPropertyNamesLength({}); // 0
    Object.getOwnPropertySymbolsLength({}); // 0
    • 无原型的对象
    const obj = Object.create(null);
    obj.property = 1;
    Object.keysLength(obj); // 1(如果可枚举)
    Object.getOwnPropertyNamesLength(obj); // 1
    const obj2 = { __proto__: null };
    obj2.property = 1;
    Object.keysLength(obj2); // 1(如果可枚举)
    Object.getOwnPropertyNamesLength(obj2); // 1
    • 数组索引键

    参见 https://tc39.es/ecma262/#array-index

    let obj = { "01": "string key", 1: "index", 2: "index" };
    Object.keysLength(obj); // 3
    
    obj = { "0": "index", "-1": "string key", "01": "string key" };
    Object.keysLength(obj); // 3
    • 基于字符串的键
    const obj = { "01": "string key", 1: "index", 2: "index" };
    Object.keysLength(obj); // 3
    Object.getOwnPropertyNamesLength(obj); // 3(如果存在不可枚举属性,则更多)
    • 基于符号的键
    const obj = { [Symbol()]: "symbol", 1: "index", 2: "index" };
    Object.getOwnPropertySymbolsLength(obj); // 1

    明确的语义

    • 仅考虑自身属性。
    • 可枚举性语义与相应的现有 API 对齐:
      • Object.keysLength:仅计算可枚举字符串键属性(包括数组索引)。
      • Object.getOwnPropertyNamesLength:计算所有字符串键属性,无论可枚举性。
      • Object.getOwnPropertySymbolsLength:计算所有符号键属性,无论可枚举性。
    • 实现必须完全避免中间数组分配。

    算法规范

    原生实现应严格避免创建中间数组或不必要的分配。对于每个方法:

    1. count 初始化为 0
    2. 直接遍历对象的自身属性键,通过其内部槽位,而不实例化数组。
    3. 对于每个自身属性键:
      • 对于 Object.keysLength:如果键是字符串且属性可枚举,则递增 count
      • 对于 Object.getOwnPropertyNamesLength:如果键是字符串(无论是否可枚举),则递增 count
      • 对于 Object.getOwnPropertySymbolsLength:如果键是符号(无论是否可枚举),则递增 count
    4. 返回 count

    参见规范文档的详细信息:

    考虑的替代方案

    • 选择的方法:多个独立方法:本文档追求三个聚焦的方法(Object.keysLengthObject.getOwnPropertyNamesLengthObject.getOwnPropertySymbolsLength),以最小化选项表面,与现有 API 形状对齐,并支持独立推进和评估。
    • 带选项对象的单一方法(Object.propertyCount(target, options):之前已考虑并拒绝。虽然灵活,但它:
      • 产生微妙的组合语义(例如,可枚举性 + 键类型)。
      • 偏离了聚焦、正交方法的既定先例(keysgetOwnPropertyNamesgetOwnPropertySymbols)。
    • 使用布尔值表示键类型(在选项对象内):作为单一方法方法的一部分被拒绝,原因是默认值令人困惑且状态空间不匹配。

    TC39 阶段和倡导者

    • 准备进入 第 2 阶段

    使用场景

    • 改进的可读性和明确意图
    • 显著的性能提升
    • 减少内存开销
    • 更简单的代码

    先例

    在广泛使用的 JavaScript 运行时、框架和库(Node.js、React、Angular、Lodash)中的频繁模式表明了对优化属性计数机制的共同需求。

    正则表达式的 exec/match/matchAll 方法会产生一个“匹配对象”,它是一个数组,上面带有非索引的字符串属性(lastIndex、groups 等)。

    考虑事项

    • 向后兼容性:完全向后兼容。
    • 性能:原生实现将通过消除中间数组显著优于现有方法。
    • 灵活性:清晰、聚焦的方法覆盖了常见情况,无需配置复杂性。
    • 简单性:提高代码可读性和清晰度。
    • 面向未来:可以根据需要添加额外的聚焦 API(例如,可枚举符号的发现和计数),而不会用选项重载单一方法。

    未来新增

    以下添加是合理的后续工作,与现有方法家族和上述分解一致:

    • Object.symbols(target):一个新方法,类似于 Object.keys(target),返回对象自身的可枚举符号键属性数组,按插入顺序排列。这填补了 Object.keys(可枚举字符串键)和 Object.getOwnPropertySymbols(所有自身符号,无论可枚举性)之间的当前空白。
    • Object.symbolsLength(target)Object.symbols(target).length 的非分配对应物,类似于 Object.keysLength。这将被规范为与 Object.keysLength 完全相同,但仅限于可枚举符号键属性。

    这些可以与上述三个方法独立地一起推进,并将为字符串和符号键提供一套完整、并行的原始方法:发现(keys/symbols)和计数(keysLength/symbolsLength),以及通过 getOwnPropertyNamesLengthgetOwnPropertySymbolsLength 获得总的字符串/符号数量。

    结论

    Object.keysLengthObject.getOwnPropertyNamesLengthObject.getOwnPropertySymbolsLength 通过高效地计算对象属性数量而不使用中间数组,提供了实质性的性能优势,增强了 ECMAScript 的清晰度、性能和减少的内存开销,同时与易于理解的现有 API 对齐。