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-deep-path-properties-for-record.md.
  • 简体中文
  • Deep Path Properties in Record Literals S1

    中文标题:Record 字面量中的深层路径属性

    提案概览
    提案速览

    该提案为 Record 字面量引入了一种新的深层路径属性语法,使描述深层嵌套结构更加简洁。它支持点语法和计算键,并且可以与展开语法结合使用,以更新不可变记录和元组中的特定嵌套字段。该提案定义了错误行为:在展开值中访问不存在的路径,或在元组上设置非数字类键,都会抛出 TypeError。

    Note

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

    Record 字面量中的深层路径属性

    面向 Record 字面量 的 ECMAScript 深层路径属性提案。

    作者:

    • Rick Button (Bloomberg)

    提案负责人:

    • Rick Button (Bloomberg)
    • Robin Ricard (Bloomberg)

    顾问:

    • Dan Ehrenberg (Igalia)

    阶段: 1,已于 2020 年 6 月 TC39 会议达到。

    概述

    Record 字面量 有时会包含深层嵌套的结构,但用于描述它们的语法(无论是作为新值,还是通过展开语法基于先前的值)可能既笨拙又冗长。面向 Record 字面量的深层路径属性为这一问题提供了解决方案,引入了一种新语法,以更简洁、更易读的方式描述深层嵌套的结构。

    示例

    这些示例展示了面向 Record 字面量的深层路径属性的一种可能语法。

    const state1 = #{
        counters: #[
            #{ name: "Counter 1", value: 1 },
            #{ name: "Counter 2", value: 0 },
            #{ name: "Counter 3", value: 123 },
        ],
        metadata: #{
            lastUpdate: 1584382969000,
        },
    };
    
    const state2 = #{
        ...state1,
        counters[0].value: 2,
        counters[1].value: 1,
        metadata.lastUpdate: 1584383011300,
    };
    
    assert(state2.counters[0].value === 2);
    assert(state2.counters[1].value === 1);
    assert(state2.metadata.lastUpdate === 1584383011300);
    
    // As expected, the unmodified values from "spreading" state1 remain in state2.
    assert(state2.counters[2].value === 123);

    在上一个示例中,两个计数器被递增,并且新记录 state2 中的 “lastUpdate” 时间被更新。

    如果没有深层路径属性,可以通过几种不同方式创建 state2

    // With records/tuples and recursive usage of spread syntax
    const state2 = #{
        ...state1,
        counters: #[
            #{
                ...state1.counters[0],
                value: 2,
            },
            #{
                ...state1.counters[1],
                value: 1,
            },
            ...state1.counters,
        ],
        metadata: #{
            ...state1.metadata,
            lastUpdate: 1584383011300,
        },
    }
    
    // With Immer (and regular objects)
    const state2 = Immer.produce(state1, draft => {
        draft.counters[0].value = 2;
        draft.counters[1].value = 1;
        draft.metadata.lastUpdate = 1584383011300;
    });
    
    // With Immutable.js (and regular objects)
    const immutableState = Immutable.fromJS(state1);
    const state2 = immutableState
        .setIn(["counters", 0, "value"], 2)
        .setIn(["counters", 1, "value"], 1)
        .setIn(["metadata", "lastUpdate"], 1584383011300);

    一个简单示例

    const rec = #{ a.b.c: 123 };
    assert(rec === #{ a: #{ b: #{ c: 123 }}});

    计算式深层路径属性键

    const rec = #{ ["a"]["b"]["c"]: 123 }
    assert(rec === #{ a: #{ b: #{ c: 123 }}});

    可以将点语法与计算键混合使用。

    const b = "b";
    const rec = #{ ["a"][b].c: 123 }
    assert(rec === #{ a: #{ b: #{ c: 123 }}});

    将深层路径属性与展开结合

    const one = #{
        a: 1,
        b: #{
            c: #{
                d: 2,
                e: 3,
            }
        }
    };
    const two = #{
        b.c.d: 4,
        ...one,
    };
    
    assert(one.b.c.d === 2);
    assert(two.b.c.d === 4);

    可以遍历 Tuples

    const one = #{
        a: 1,
        b: #{
            c: #[2, 3, 4, #[5, 6]]
        },
    }
    const two = #{
        b.c[3][1]: 7,
        ...one,
    };
    
    assert(two.b.c === #[2, 3, 4, #[5, 7]]);

    常见问题

    深层路径属性语法支持对象吗?

    不支持。与 Record 相比,对象上深层路径属性的语义要模糊得多;而在 Record 中(由于 Record 是不可变的)语义要简单得多。

    如果深层路径属性在展开的值中不存在,会发生什么?

    会抛出 TypeError。例如:

    const one = #{ a: #{} };
    
    #{ ...one, a.b.c: "foo" }; // throws TypeError
    
    #{ ...one, a.b[0]: "foo" }; // also throws TypeError

    人们可能会期望在这类情况下以某种方式“物化”出一个 record 或 tuple 来补全路径。然而,当深层路径属性语法与展开语法一起使用时,如果值尚不存在,那么“物化”出哪种值可能存在歧义。在后一个示例中,以下两种展开似乎都是有效的答案:

    #{ a: #{ b: #[123] } }
    
    #{ a: #{ b: #{ 0: 123 } } }

    为了保持简单和最小化,如果在展开值中路径尚不存在,则尝试使用深层路径属性(无论是否存在这种歧义)都会抛出 TypeError

    如果深层路径属性尝试在 Tuple 上设置一个非数字类键,会发生什么?

    会抛出 TypeError。例如:

    const one = #{ a: #[1,2,3] };
    
    #{ ...one, a.foo: 4 }; // throws TypeError

    Tuples 不能拥有非数字类键,因为它们是值的有序不可变列表,没有“属性”这个概念(就像 number 没有属性一样)。如果你尝试使用深层路径属性语法创建一个 Record 字面量,而该语法会生成一个带有非数字类键的 Tuple,则会抛出 TypeError

    更多讨论请参见 issue #4