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/proposal-object-getownpropertydescriptors.md.
  • 简体中文
  • Object.getOwnPropertyDescriptors S4

    提案概览
    提案速览

    该提案引入了 Object.getOwnPropertyDescriptors,作为现有单数方法的复数对应项,允许在一次调用中检索对象的所有自有属性描述符。它旨在简化常见的模式,如浅拷贝、合并和混入,避免 Object.assign 的陷阱,例如调用 getter 和吞没行为。

    Note

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

    Object.getOwnPropertyDescriptors 提案(Polyfill

    冠军(Champion)

    在阶段 0,Rick Waldron 同意支持此提案。 然而,当前 的官方冠军是 Jordan Harband

    状态

    此提案目前处于 TC39 流程第 4 阶段

    此提案可能与 Reflect.getOwnPropertyDescriptors 相同,但为了与其他复数版本保持一致,它被描述为 Object 的公共静态方法。

    动机

    ECMAScript 中没有单一方法能够简化两个对象之间的正确复制。如今,函数式编程和不可变对象已成为复杂应用的重要组成部分,每个框架或库都在实现自己的样板代码,以便在组合对象或原型之间正确复制属性。

    在使用 Object.assign 作为回退方案时,存在很多混乱,且大多数时候会产生不希望的行为,因为它的复制方式会吞没行为:它直接访问属性和符号而不是其描述符,丢弃了可能的访问器,这可能在组合更复杂的对象或类原型时导致危险。

    检索所有描述符(无论是否可枚举)也是实现基于 class 及其原型的组合的关键,因为默认情况下,它们具有不可枚举的方法和访问器。

    装饰器也可以轻松地一次性从另一个类或混入中获取所有描述符,并通过 Object.defineProperties 分配它们。过滤不需要的描述符也会更简单,且每次需要时减少重复。

    最后但同样重要的是,与 Object.assign 相比,两个未知对象之间的浅拷贝将不会出现意外。

    常见问题解答

    是否应该有 Reflect.getOwnPropertyDescriptors

    由于此提案的主要目标是简化一些常见的样板代码,并与存在单一版本方法但缺少复数版本的事实保持一致,因此进一步保持一致性,也可能需要添加当前 Reflect.getOwnPropertyDescriptor 方法的复数版本。

    更新:委员会此前已决定 Reflect 仅用于镜像 Proxy 陷阱,因此这不是一个选项。

    提议的解决方案

    作为 Object.getOwnPropertyDescriptor 的复数版本,此提案涉及在一次操作中检索通用对象的所有自有描述符。

    此提案的 polyfill 可能如下所示:

    if (!Object.hasOwnProperty('getOwnPropertyDescriptors')) {
      Object.defineProperty(
        Object,
        'getOwnPropertyDescriptors',
        {
          configurable: true,
          writable: true,
          value: function getOwnPropertyDescriptors(object) {
            return Reflect.ownKeys(object).reduce((descriptors, key) => {
              return Object.defineProperty(
                descriptors,
                key,
                {
                  configurable: true,
                  enumerable: true,
                  writable: true,
                  value: Object.getOwnPropertyDescriptor(object, key)
                }
              );
            }, {});
          }
        }
      );
    }

    示例

    polyfill 展示了一种替代方案,友好于 ES2015,改进了需要兼容 ES5 或部分 ES2015 引擎的样板代码。

    现在 Object.getOwnPropertyDescriptors 已就位,在两个对象之间进行真正浅拷贝或克隆操作所需的一切,如下例所示:

    const shallowClone = (object) => Object.create(
      Object.getPrototypeOf(object),
      Object.getOwnPropertyDescriptors(object)
    );
    
    const shallowMerge = (target, source) => Object.defineProperties(
      target,
      Object.getOwnPropertyDescriptors(source)
    );

    基于对象的可能的混入解决方案也可以从本提案中受益:

    let mix = (object) => ({
      with: (...mixins) => mixins.reduce(
        (c, mixin) => Object.create(
          c, Object.getOwnPropertyDescriptors(mixin)
        ), object)
    });
    
    // 多个混入示例
    let a = {a: 'a'};
    let b = {b: 'b'};
    let c = {c: 'c'};
    let d = mix(c).with(a, b);

    假设你想要一个 Object.assign 的版本,它使用 [[DefineOwnProperty]]/[[GetOwnProperty]] 而不是 [[Set]]/[[Get]],以避免副作用并复制 setter/getter,但仍然使用可枚举性作为区分因素。

    在提案之前,这样的方法可能如下所示:

    function completeAssign(target, ...sources) {
      sources.forEach(source => {
        // 获取键的描述符
        let descriptors = Object.keys(source).reduce((descriptors, key) => {
          descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
          return descriptors;
        }, {});
        // 默认情况下,Object.assign 也会复制可枚举的 Symbol
        // 因此也获取并过滤 Symbol
        Object.getOwnPropertySymbols(source).forEach(sym => {
          let descriptor = Object.getOwnPropertyDescriptor(source, sym);
          if (descriptor.enumerable) {
            descriptors[sym] = descriptor;
          }
        });
        Object.defineProperties(target, descriptors);
      });
      return target;
    }

    然而,如果 Object.getOwnPropertyDescriptors 可用,上述样板代码将如下所示:

    var completeAssign = (target, ...sources) =>
      sources.reduce((target, source) => {
        let descriptors = Object.getOwnPropertyDescriptors(source);
        Reflect.ownKeys(descriptors).forEach(key => {
          if (!descriptors[key].enumerable) {
            delete descriptors[key];
          }
        });
        return Object.defineProperties(target, descriptors);
      }, target);