🌐 US-Proxy
class="h-entry status-LS">

基础设施

现行标准 — 最后更新

参与:
GitHub whatwg/infra(新建议,公开议题)
在 Matrix 聊天
提交:
GitHub whatwg/infra/commits
此提交的快照
测试:
web-platform-tests infra/ (持续工作)
翻译 (非规范性):
日本語
简体中文
한국어

摘要

基础设施标准(Infra Standard)旨在定义标准制定所依赖的基础性概念。

目标

欢迎提出更多目标建议。

1. 用法

要在标题为 X 的文档中使用本标准,请写:

X 依赖于 Infra。[Infra]

此外,强烈建议交叉引用所有相关术语,以避免歧义。

2. 约定

2.1. 一致性

所有断言、图示、示例和注释均为非规范性内容,所有明确标记为非规范性的章节也是非规范性的。其他所有内容均为规范性内容。

关键词 "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"NOT RECOMMENDED"、"MAY" 和 "OPTIONAL" 的解释方式见 RFC 2119。[RFC2119]

这些关键词在小写时等价,并且不得出现在非规范性内容中。

这对 RFC 8174 属于有意违规,目的是提高可读性,并延续许多非 IETF 发布的 RFC 8174 之前文档中的长期做法。[RFC8174]

上述内容适用于本标准及所有使用本标准的文档。鼓励使用本标准的文档仅使用 "must"、"must not"、"should" 和 "may",并采用小写形式,因为这样通常更易读。

对于非规范性内容,可以使用 "strongly encouraged"、"strongly discouraged"、"encouraged"、"discouraged"、"can"、"cannot"、"could"、"could not"、"might" 和 "might not" 等表达。

2.2. 与其他规范的一致性

一般来说,规范会与多种其他规范交互并依赖于它们。在某些情况下,不同规范之间的冲突需求可能导致某一规范不得不违反其他规范的要求。当发生这种情况时,使用 Infra 标准的文档应将此类违规标注为有意违规,并说明违规原因。

上一节 § 2.1 一致性 记录了 Infra 针对 RFC 8174 的一次有意违规。

2.3. 术语

当 "或"(or)一词既可能为包含“或”又可能为排他“或”时(例如,“if either width or height is zero”,如果宽或高有一个为零),其含义为包含“或”(即“或两者皆是”),除非特别指出为排他“或”(如“但不能两者都为真”)。


用户代理是指代表用户行动的任何软件实体,例如获取和呈现 Web 内容、协助最终用户与内容交互的浏览器等。在使用 Infra 标准的规范中,用户代理通常指实现该规范的客户端软件的一个实例。该客户端软件本身称为实现。 一个人在日常生活中可以使用许多不同的用户代理,例如通过配置一个实现来同时作为多个用户代理,如使用多个用户配置文件或实现的隐私浏览模式等。

如果某事被声明为实现自定义(implementation-defined),则具体细节由实现自定义的内容由实现决定。如无此类声明,则反之:实现必须遵循使用本标准的文档所规定的规则。

以实现自定义的方式在 input 中插入 U+000A(LF)码点,以使每一行不超过 width 个码点。对于此要求,行由 input 的起始、末尾以及 U+000A(LF)分隔。

2.4. 隐私问题

使用 Infra 标准的文档中定义的某些功能,可能会以牺牲部分用户隐私换取用户便利。

通常,由于互联网的架构,用户可以通过其 IP 地址与其他用户区分开。IP 地址并不能完全对应某个用户;当用户在不同设备或网络间切换时,IP 地址会变化;同样,NAT 路由、代理服务器和共享计算机等也会导致表面上来自同一 IP 的数据包实际对应多个用户。洋葱路由(onion routing)等技术还能进一步匿名化请求,使一个用户在网络某节点的请求表现为来自网络的多个不同位置。[RFC791]

然而,用户请求所用的 IP 地址并不是唯一可能将这些请求相关联的机制。例如,Cookie 就是为此而设计的,也是 Web 上大多数会话功能的基础,这些功能使你可以登录拥有账号的网站。更广泛地说,任何缓存机制或共享状态(包括但不限于 HSTS、HTTP 缓存、连接分组、存储 API 等)都可能被滥用。[COOKIES] [RFC6797] [STORAGE]

还有一些更隐蔽的机制。用户系统的某些特征可用于将不同用户群体区分开。收集足够多的信息后,可以计算出用户浏览器的“数字指纹”,这比 IP 地址更能判断哪些请求来自同一用户。

以这种方式关联请求,尤其跨站点时,可能被用于恶意目的,例如政府将某人使用某站点获取驾车导航时的家庭住址与其在某些论坛的参与情况结合起来,以确定该人是否应该被剥夺投票权。

由于恶意目的可能极其恶劣,强烈建议用户代理实现者和规范作者尽量减少可能被用于指纹识别或跟踪用户的信息泄露。

不幸的是,正如本节第一段所述,有时出于功能考虑需要开放某些 API,这些 API 也容易被用于指纹识别和跟踪,所以不能一概封堵。例如,允许登录网站以特定身份发帖,必然要求可以识别来自同一用户的所有请求。更微妙的是,比如文本宽度信息对于许多涉及 canvas 绘制文本效果(如加边框等)都必不可少,但这也可能泄露可用于分组请求的信息(例如通过暴力枚举推测用户安装了哪些字体,而字体信息因人而异)。

(This is a tracking vector.) 在使用 Infra 标准的文档中,能被用作跟踪向量的功能会像本段一样标注。

平台上的其他特性也可能被用于同样目的,包括但不限于:

3. 算法

3.1. 一致性

算法及其步骤中以祈使句表达的要求(如“去除所有前导空格”或“返回 false”),应根据引入该算法或步骤时所用关键词(如“must”)来理解其含义。如果未使用此类关键词,则默认视为 must。

例如,若规范写道:

要吃一个橙子,用户必须:

  1. 剥橙子的皮。
  2. 将橙子分成一片片。
  3. 吃掉橙子片。

这等价于:

要吃一个橙子:

  1. 用户必须剥橙子的皮。
  2. 用户必须将橙子分成一片片。
  3. 用户必须吃掉橙子片。

这里的关键词是“must”。

如果上述算法仅以“要吃一个橙子:”引入,含义不变,因默认即为 must。

以算法或具体步骤表达的一致性要求,可以以任何方式实现,只要最终结果等价即可。(这些算法本意是易于理解,而非高性能。)

性能很难做到绝对正确,因为它受用户感知、计算机架构以及随时间变化的输入类型影响。例如,JavaScript 引擎通常会针对标准化为单一算法的内容实现多种代码路径,以优化速度或内存消耗。将所有这些代码路径标准化几乎不可能,也无益于标准的长期生命力,因此性能应作为业界竞争领域。

3.2. 避免对算法输入的限制

使用 Infra 标准的文档通常不应对算法输入的大小、资源使用等施加具体限制。这有助于用户代理间的竞争,也避免限制未来的计算需求。

(This is a tracking vector.) 但用户代理可对本应不受约束的输入施加实现自定义限制。例如防止拒绝服务攻击、防止内存耗尽或绕过平台特定限制等。

全局资源限制可被用作资源耗尽攻击的侧信道,攻击者可通过观察受害应用是否达到全局限制来推断。若资源限制因硬件差异而显著不同,也可用于指纹识别用户代理。

一个允许创建内存位图的 API 可以规范为允许任意尺寸,或允许尺寸不超过 JavaScript 的 Number.MAX_SAFE_INTEGER。但实现可以选择对尺寸加以实现自定义(未规范)限制,而不是尝试分配巨量内存。

某编程语言可能未规定最大调用栈深度。但实现可以出于实际需要加以限制。

由于某些代码可能依赖具体的限制,为了互操作性,有时规定一个下限是有益的。这对未来发展无碍,也能让代码在更多用户代理下运行。

对实现自定义的限制规定一个下限也有助于互操作性,即确保所有实现都能处理至少某个规模的输入。

3.3. 声明

算法名通常为动词短语,但有时会采用强调其独立性的名称,便于标准和读者更习惯地引用该算法。

后一类算法名称的例子包括“属性变更步骤”、“内部模块脚本图获取过程”和“重载决议算法”。

声明算法时应写明其名称、参数和返回类型,格式如下:

要【算法名】,给定一个[type1] 参数1、一个[type2] 参数2,……,执行以下步骤。返回值为[返回类型]。

(对于非动词短语算法名,使用“要执行【算法名】……”。更复杂的参数声明格式见§ 3.4 参数。)

要解析一个很棒的格式,给定一个字节序列 bytes,执行以下步骤。返回字符串或 null。

不返回值的算法可用更简洁的格式声明。即使有返回值,若返回类型易于从步骤推断,也可采用该格式:

要【算法名】,给定[type1] 参数1、[type2] 参数2,……:

要解析一个很棒的格式,给定字节序列 bytes:

极短的算法可以用一句话声明和描述:

要解析一个很棒的格式,给定字节序列 bytes,返回bytes经同构解码后再ASCII 大写化的结果。

算法声明应包含参数类型,但若参数名足够明确,或上下文足够清楚,也可省略类型(如只对其他算法简单包装时)。

要加载经典脚本,给定 url,返回用 url 和 "classic" 调用内部脚本加载算法的结果。

3.4. 参数

算法参数一般按§ 3.3 声明所述顺序列出。但也有更复杂的情况。

算法参数可以是可选的,声明算法时须予以说明,并置于所有必选参数之后。可选参数可设默认值,也可在算法体内判断参数是否给出。具体格式如下:

……一个可选[type] 参数……

……一个可选[type] 参数(默认值为[默认值])……

可选布尔参数必须指定默认值,且默认为 false。

要导航至资源 resource,可选字符串 navigationType,可选布尔 exceptionsEnabled(默认为 false):

  1. ……
  2. 如果给定 navigationType,则对其进行相关操作。
  3. ……

调用带可选位置参数的算法时,可省略尾部可选参数。

对上述算法的调用形式如:

但无法仅为第三个(exceptionsEnabled)参数赋非默认值而不传第二个(navigationType);且最后一种调用对读者不直观,因为需要追溯参数顺序。请继续阅读下文解决办法!

可选命名参数(非位置参数)可提升调用处的清晰度和灵活性。这类参数应同时标记为变量和定义,并在调用处链接。

要导航至资源 resource,可选字符串 navigationType,可选布尔 exceptionsEnabled(默认为 false):

  1. ……
  2. 如给定 navigationType,则对其进行相关操作。
  3. ……

调用形式如:

注意,在算法步骤中,参数值只是变量引用,不链接到参数声明;只有调用处才链接参数声明。

非可选的命名参数同样可以用上述方式标记并在调用处链接,有助于提升调用处的清晰度。

布尔参数无论是否可选,均建议使用命名参数,详见 The Pitfalls of Boolean Trap。

另一种提升清晰度的方法是将相关值打包为结构体(struct),并作为参数传递,尤其适用于多算法共用同一组相关值的场景。

3.5. 变量

变量使用 “let” 声明,使用 “set” 修改。

令 list 为新的列表。

  1. 令 value 为 null。

  2. 如果 input 是字符串,则将 value 设为 input。

  3. 否则,将 value 设为 input 经UTF-8 解码后所得。

  4. 断言:value 是字符串。

令 activationTarget 为 isActivationEvent 为 true 且 target 具有激活行为时的 target,否则为 null。

变量在声明前不得使用。变量具有块作用域。每个算法中变量不得重复声明。

可以使用多重赋值语法将多个变量赋给 元组的 项,方法是用 U+0028 LEFT PARENTHESIS 和 U+0029 RIGHT PARENTHESIS 将变量 名称括起来,并用 U+002C (,) 分隔每个变量名称。被赋值的变量数量 不能与 项在 元组中的数量不同。

  1. 令 statusInstance 为状态(200,`OK`)。

  2. 令 (status, statusMessage) 为 statusInstance。

将 status 和 statusMessage 赋值也可分为两步,分别用索引或名称访问元组的项。

3.6. 控制流

算法的控制流中,"return" 或 "throw" 的要求会终止当前语句所在的算法。"return" 可将给定值返回给调用者。"throw" 会让调用者自动重新抛出给定值,并终止调用者算法。调用者可用叙述捕获异常并执行其他操作。

3.7. 条件中止

有时需要在某个条件变为真时停止执行一系列步骤。

此时,应声明该系列步骤将在特定 condition 满足时中止(abort when)。这表明这些步骤应在每一步前插入一条判断 condition 的语句,若其为真,则跳过剩余步骤。

若算法如此设计,则后续步骤可标注为中止时(if aborted),即若前面某步因 abort when 条件为真而被跳过,则该步必须执行。

如下算法:

  1. 令 result 为空列表。

  2. 执行以下步骤,中止条件为用户点击“取消”按钮:

    1. 计算 π 的前一百万位,并追加到 result。

    2. 计算 e 的前一百万位,并追加到 result。

    3. 计算 φ 的前一百万位,并追加到 result。

  3. 如中止,追加 "未完成!" 到 result。

等价于如下更冗长写法:

  1. 令 result 为空列表。

  2. 若用户未点击“取消”按钮:

    1. 计算 π 的前一百万位,并追加到 result。

    2. 若用户未点击“取消”按钮:

      1. 计算 e 的前一百万位,并追加到 result。

      2. 若用户未点击“取消”按钮,则计算 φ 的前一百万位,并追加到 result。

  3. 若用户点击了“取消”按钮,则追加"未完成!"到 result。

使用该结构时,实现允许在指定步骤期间而非每步前后检查 condition,只要最终结果不变即可。例如,上述例子中只要 result 在运算期间未被修改,用户代理可中断计算。

3.8. 条件语句

带条件语句的算法应使用 "if"、"then" 和 "otherwise" 关键词。

  1. 令 value 为 null。

  2. 如果 input 是字符串,则将 value 设为 input。

  3. 返回 value。

一旦使用 "otherwise",就不再用 "then"。

  1. 令 value 为 null。

  2. 如果 input 是字符串,则将 value 设为 input。

  3. 否则,将 value 设为失败。

  4. 返回 value。

  1. 令 value 为 null。

  2. 如果 input 是字符串,则将 value 设为 input。

  3. 否则,若 input 是字符串列表,则将 value 设为 input[0]。

  4. 否则,抛出 TypeError。

  5. 返回 value。

3.9. 循环

可用多种方式重复执行一组步骤直到满足某条件。

Infra 标准尚未穷尽所有迭代写法,如有需要请提 issue。

For each

如列表(及其衍生物)和映射中定义。

While

指令在条件满足时重复执行一组步骤。

当 condition 为 "met" 时:

  1. ……

迭代流程可用 continue(继续)或 break(中断)控制。continue 会跳过本次迭代剩余步骤进入下一个元素,如无剩余元素则终止。break 则跳过本次及后续所有元素,终止迭代。

令 example 为列表 « 1, 2, 3, 4 »。下述叙述会对 1、2、3、4 依次执行 operation:

  1. 对于每个 item 属于 example:

    1. 对 item 执行 operation。

下述叙述会对 1、2、4 执行 operation,跳过 3。

  1. 对于每个 item 属于 example:

    1. 若 item 为 3,则continue。
    2. 对 item 执行 operation。

下述叙述会对 1、2 执行 operation,跳过 3、4。

  1. 对于每个 item 属于 example:

    1. 若 item 为 3,则break。
    2. 对 item 执行 operation。

3.10. 断言

为提升可读性,可在算法中添加断言,用于表达不变量。写法为“断言:”,后接必须为真的语句。若该语句为假,则表明使用 Infra 标准的文档存在问题,应予以报告和修正。

因断言永远为真,对实现无要求。

  1. 令 x 为 "Aperture Science"。

  2. 断言:x 等于 "Aperture Science"。

3.11. 同一性与相等性

值 a 是(或相同于)一个值 b,如果它们是同一个值。对于原始数据类型的值,这意味着它们具有 相同的内容,例如,两个字符串是 相同的,如果它们由相同的序列组成 ,该序列由码元组成。对于其他数据 类型的值,例如列表、 映射和结构体,这意味着它们是同一个实例;两个这样的 值 如果是分别创建的,则彼此绝不会相同,即使它们的内容 相同。

一种数据类型具有一种相等性, 它是一种接受该数据类型的两个值并 返回一个布尔值的算法。 除非另有定义,否则一种数据类型的 相等性在两个值彼此 同一时返回 true;否则返回 false。本标准为列表、 映射和元组定义了不同的相等性。

值 a 等于(或与) 值 b 相等,如果 a 是 b,或者 a 和 b 属于相同的数据类型,并且该数据类型的 相等性在给定 a 和 b 时返回 true。

键值是一个示例元组,由一个键 (一个字符串)和一个值(一个由数字组成的列表)组成。

  1. 令 original 为键值 ("x", « 1, 2, 3, 4 »)。

  2. 令 alsoOriginal 为 original。

  3. 令 copy 为键值 ("x", « 1, 2, 3, 4 »)。

  4. 断言:original 是 alsoOriginal。

  5. 断言:original 不是 copy。

  6. 断言:original 等于 copy,因为它们的 键彼此 同一,并且它们的值 相等。

由于列表、映射和元组的相等性会比较 它们的内容并使用等于,因此应避免比较 直接或 间接包含自身的值,因为这可能导致无限循环:

  1. 令 a 为 « »。

  2. 将 a 追加到 a。

  3. 令 b 为 « »。

  4. 将 b 追加到 b。

  5. 如果 a 等于 b,那么……

最后一步永远不会结束:a 和 b 彼此并不同一,但具有 相同的大小,因此确定 它们是否相等需要 确定 a[0] 是否等于 b[0],而这又是同一个问题,因为 a[0] 是 a,而 b[0] 是 b。

4. 基本数据类型

4.1. 空值(Nulls)

值 null 用于表示没有值。它可与 JavaScript 的 null 值互换使用。[ECMA-262]

令 element 为 null。

如果 input 是空字符串,则返回 null。

4.2. 布尔值(Booleans)

布尔值只能为 true 或 false。

令 elementSeen 为 false。

4.3. 数字

数字很复杂,请参见 issue #87。我们希望将来能够为类型和数学运算提供更多指导,欢迎贡献!


8位无符号整数是范围为 0 到 255(0 到 28 − 1,包括端点)的整数。

16位无符号整数是范围为 0 到 65535(0 到 216 − 1,包括端点)的整数。

32位无符号整数是范围为 0 到 4294967295(0 到 232 − 1,包括端点)的整数。

64位无符号整数是范围为 0 到 18446744073709551615(0 到 264 − 1,包括端点)的整数。

128位无符号整数是范围为 0 到 340282366920938463463374607431768211455(0 到 2128 − 1,包括端点)的整数。

IPv6 地址是128位无符号整数。


8位有符号整数是范围为 −128 到 127(−27 到 27 − 1,包括端点)的整数。

16位有符号整数是范围为 −32768 到 32767(−215 到 215 − 1,包括端点)的整数。

32位有符号整数是范围为 −2147483648 到 2147483647(−231 到 231 − 1,包括端点)的整数。

64位有符号整数是范围为 −9223372036854775808 到 9223372036854775807(−263 到 263 − 1,包括端点)的整数。


如果两个数是 相同的数学值,则它们彼此同一。

4.4. 字节(Bytes)

字节(byte)是一组八位比特,表示为“0x”加两个ASCII 大写十六进制数字,范围为 0x00 到 0xFF,包括端点。字节的值(value)就是其底层数字值。

0x40 是一个字节,其值为 64。

ASCII 字节是指在 0x00(NUL)到 0x7F(DEL)范围内的字节。如示例所示,除 0x28 和 0x29 外,ASCII 字节可在括号中附带 ASCII format for Network Interchange 标准码表中的描述。[RFC20]

0x28 可写作 "(left parenthesis)",0x29 可写作 "(right parenthesis)"。

0x49 (I) 经UTF-8 解码后为码点 U+0049 (I)。

4.5. 字节序列(Byte sequences)

一个 字节序列是一个 由字节组成的序列,表示为一个 以空格分隔的 字节序列。范围在 0x20 (SP) 到 0x7E (~)(含)之间的字节所组成的字节序列,可以 alternatively 写成字符串,但使用反引号而不是引号,以避免 与实际的字符串混淆。

0x48 0x49 也可以表示为 `HI`。

标头(例如 `Content-Type`)是字节序列。

要从字符串获取字节序列,建议使用 Encoding 中的UTF-8 编码。在极少数情况下,可能需要同构编码。 [ENCODING]

一个字节序列的长度是它所包含的 字节的数量。

要对一个字节序列进行字节小写化,将其中每个 位于 0x41 (A) 到 0x5A (Z)(含)范围内的 字节 增加 0x20。

要对一个字节序列进行字节大写化,将其中每个 位于 0x61 (a) 到 0x7A (z)(含)范围内的 字节 减去 0x20。

如果一个字节序列 A 的 字节不区分大小写匹配另一个 字节序列 B,则 A 的字节小写化结果 与 B 的字节小写化结果 相同。

如果两个字节序列由 相同的 字节序列组成,则它们彼此相同。


如果以下步骤返回 true,则字节序列 potentialPrefix 是 字节序列 input 的前缀:

  1. 令 i 为 0。

  2. 当 true 时:

    1. 如果 i 大于或等于 potentialPrefix 的 长度,则返回 true。

    2. 如果 i 大于或等于 input 的 长度,则返回 false。

    3. 令 potentialPrefixByte 为 potentialPrefix 的第 i 个字节。

    4. 令 inputByte 为 input 的第 i 个字节。

    5. 如果 potentialPrefixByte 不是 inputByte,则返回 false。

    6. 将 i 设为 i + 1。

“input 以 potentialPrefix 开头”可以用作“potentialPrefix 是 input 的前缀”的同义表述。

如果以下步骤返回 true,则字节序列 a 字节小于一个字节序列 b:

  1. 如果 b 是 a 的前缀,则返回 false。

  2. 如果 a 是 b 的前缀,则返回 true。

  3. 令 n 为最小的索引,使得 a 的第 n 个字节 不同于 b 的第 n 个字节。(这样的 索引必然存在,因为两个字节序列均不是另一个的前缀。)

  4. 如果 a 的第 n 个字节小于 b 的第 n 个字节,则返回 true。

  5. 返回 false。


要同构 解码一个字节 序列 input,返回一个 字符串,其码点长度 等于 input 的 长度,并且 其码点的 值与 input 的值所对应的字节具有相同的 顺序。

4.6. 码点(Code points)

码点指 Unicode 码点,表示为 "U+" 后跟四到六个ASCII 大写十六进制数字,范围是 U+0000 到 U+10FFFF(包括端点)。码点的值即其底层数字。

码点后可以跟其名称、在括号中的显示形式(如不是 U+0028 或 U+0029),或两者都跟。建议使用 Infra 标准的文档,在码点无法显示或为 U+0028/U+0029 时,在其后附上名称;否则,为提升可读性,在其后附上括号内的显示形式。

码点的名称定义在 Unicode 中,用ASCII 大写字母表示。[UNICODE]

渲染为 🤔 的码点表示为 U+1F914。

引用该码点时,可写作 "U+1F914 (🤔)" 以提供额外信息。文档也可写作 "U+1F914 THINKING FACE (🤔)",但略显冗长。

难以明确显示的码点,如 U+000A,可写作 "U+000A LF"。U+0029 可写作 "U+0029 RIGHT PARENTHESIS",即使能显示,以避免括号不匹配。

码点有时也被称为字符(character),有些场合前缀会用 "0x" 而非 "U+"。

前导代理项(leading surrogate)是指范围在 U+D800 至 U+DBFF(包括端点)的码点。

尾随代理项(trailing surrogate)是范围在 U+DC00 至 U+DFFF(包括端点)的码点。

代理项(surrogate)即前导代理项或尾随代理项。

标量值(scalar value)是指不是代理项的码点。

非字符(noncharacter)是指在 U+FDD0 至 U+FDEF(包括端点),或等于 U+FFFE、U+FFFF、U+1FFFE、U+1FFFF、U+2FFFE、U+2FFFF、U+3FFFE、U+3FFFF、U+4FFFE、U+4FFFF、U+5FFFE、U+5FFFF、U+6FFFE、U+6FFFF、U+7FFFE、U+7FFFF、U+8FFFE、U+8FFFF、U+9FFFE、U+9FFFF、U+AFFFE、U+AFFFF、U+BFFFE、U+BFFFF、U+CFFFE、U+CFFFF、U+DFFFE、U+DFFFF、U+EFFFE、U+EFFFF、U+FFFFE、U+FFFFF、U+10FFFE 或 U+10FFFF 的码点。

ASCII 码点是指范围 U+0000 NULL 到 U+007F DELETE(包括端点)的码点。

ASCII 制表符或换行符为 U+0009 TAB、U+000A LF 或 U+000D CR。

ASCII 空白符为 U+0009 TAB、U+000A LF、U+000C FF、U+000D CR 或 U+0020 SPACE。

"Whitespace" 是物质名词。

XML、JSON 和部分 HTTP 规范在其空白符定义中排除了 U+000C FF:

除非规范仅涉及 XML/JSON/HTTP,否则为新特性建议使用 Infra 的ASCII 空白符定义。

C0 控制符是指范围 U+0000 NULL 到 U+001F INFORMATION SEPARATOR ONE(包括端点)的码点。

C0 控制符或空格是C0 控制符或 U+0020 SPACE。

控制符指C0 控制符或范围 U+007F DELETE 到 U+009F APPLICATION PROGRAM COMMAND(包括端点)的码点。

ASCII 数字是指范围 U+0030 (0) 到 U+0039 (9)(包括端点)的码点。

ASCII 大写十六进制数字是ASCII 数字或范围 U+0041 (A) 到 U+0046 (F)(包括端点)的码点。

ASCII 小写十六进制数字是ASCII 数字或范围 U+0061 (a) 到 U+0066 (f)(包括端点)的码点。

ASCII 十六进制数字是ASCII 大写十六进制数字或ASCII 小写十六进制数字。

ASCII 大写字母是范围 U+0041 (A) 到 U+005A (Z)(包括端点)的码点。

ASCII 小写字母是范围 U+0061 (a) 到 U+007A (z)(包括端点)的码点。

ASCII 字母是ASCII 大写字母或ASCII 小写字母。

ASCII 字母数字是ASCII 数字或ASCII 字母。

4.7. 字符串(Strings)

一个字符串是一个 16 位无符号 整数序列,也称为码元。一个 字符串也称为JavaScript 字符串。 字符串用双引号和 等宽字体表示。

"Hello, world!" 是一个字符串。

这与 Unicode 定义“码元”的方式不同。具体而言, 它 仅指 Unicode 针对 Unicode 16 位字符串所作的定义。[UNICODE]

一个字符串也可以解释为 包含码点,按照 JavaScript 规范中字符串类型一节所定义的转换。 [ECMA-262]

此转换过程会将代理项对转换为其对应的 标量值,并将所有 剩余的代理项映射到其对应的码点, 实际上保持它们原样。

一个由 码元 0xD83D、0xDCA9 和 0xD800 组成的字符串, 当解释为包含码 点时, 将由码点 U+1F4A9 和 U+D800 组成。

一个字符串的 长度 是它所包含的码元的数量。

一个字符串的 码点长度是它所包含 的码点的数量。


为了表示对字符串所能包含的 码点施加额外 限制, 本规范定义了ASCII 字符串、同构 字符串和 标量值 字符串。使用这些术语可以提高规范的清晰度。

一个ASCII 字符串是一个字符串,其码点全部是 ASCII 码点。

一个同构字符串是 一个字符串,其码点全部位于 U+0000 NULL 到 U+00FF (ÿ)(含)范围内。

一个标量值字符串 是一个字符串,其码点全部是 标量值。

对于任何涉及 I/O 或其他 会用到UTF-8 编码的操作, 标量值字符串都很有用。


要将一个字符串转换为一个标量值字符串,将所有代理项替换为 U+FFFD (�)。

被替换的代理项绝不会是代理项对的一部分,因为将 字符串解释为包含码 点的过程已经把代理项对转换成了 标量值。

一个标量值 字符串始终可以隐式用作字符串,因为每个 标量值 字符串都是一个字符串。另一方面, 一个字符串只有在 已知不包含代理项时,才能隐式用作标量值字符串; 否则就要执行一次转换。

根据实现实际最终如何表示字符串和标量值 字符串,实现很可能必须执行显式转换。仅对于 字符串本身,实现通常也会出于性能和内存 方面的原因采用多种实现方式。


如果一个字符串 a是或 相同于一个字符串 b,则它们由相同的 码元序列组成。

除非另有说明,所有字符串比较均使用是。

这种字符串 比较在 HTML 中以前称为“区分大小写” 比较。彼此比较为相同的字符串 不仅对大小写变化(例如大写和小写)敏感,还对其他码 点编码选择敏感,例如规范化形式或组合标记的顺序。两个按照 Unicode 在视觉上甚至规范等价的字符串,仍可能彼此并不 相同。[HTML] [UNICODE]

如果以下步骤返回 true,则一个字符串 potentialPrefix 是 一个字符串 input 的码元前缀, 其中该字符串为字符串:

  1. 令 i 为 0。

  2. 当 true 时:

    1. 如果 i 大于或等于 potentialPrefix 的 长度,则 返回 true。

    2. 如果 i 大于或等于 input 的长度, 则返回 false。

    3. 令 potentialPrefixCodeUnit 为 potentialPrefix 的第 i 个码元。

    4. 令 inputCodeUnit 为 input 的第 i 个码元。

    5. 如果 potentialPrefixCodeUnit 不是 inputCodeUnit,则返回 false。

    6. 将 i 设为 i + 1。

当从上下文可以清楚知道所涉及的是码 元时,例如因为其中一个 字符串是只包含 U+0020 SPACE 到 U+007E (~) 范围内字符的字面量, “input 以 potentialPrefix 开头”可以用作 “potentialPrefix 是 input 的码元前缀”的同义表述。

对于 未知值,最好明确表述: targetString 是 userInput 的码元前缀。但对于 字面量,我们可以使用更直白的语言:userInput 以 "!" 开头。

如果以下步骤返回 true,则一个字符串 potentialSuffix 是 一个字符串 input 的码元后缀, 其中该字符串为字符串:

  1. 令 i 为 1。

  2. 当 true 时:

    1. 令 potentialSuffixIndex 为 potentialSuffix 的 长度 − i。

    2. 令 inputIndex 为 input 的长度 − i。

    3. 如果 potentialSuffixIndex 小于 0,则返回 true。

    4. 如果 inputIndex 小于 0,则返回 false。

    5. 令 potentialSuffixCodeUnit 为第 potentialSuffixIndex 个 码元,其所属字符串为 potentialSuffix。

    6. 令 inputCodeUnit 为 input 的第 inputIndex 个码元。

    7. 如果 potentialSuffixCodeUnit 不是 inputCodeUnit,则返回 false。

    8. 将 i 设为 i + 1。

当从上下文可以清楚知道所涉及的是码 元时,例如因为其中一个 字符串是只包含 U+0020 SPACE 到 U+007E (~) 范围内字符的字面量, “input 以 potentialSuffix 结尾”可以用作 “potentialSuffix 是 input 的码元后缀”的同义表述。

对于 未知值,最好明确表述: targetString 是 domain 的码元后缀。但对于 字面量,我们可以使用更直白的语言:domain 以 "." 结尾。


如果以下步骤返回 true,则一个字符串 a码元小于一个 字符串 b:

  1. 如果 b 是 a 的码元前缀,则返回 false。

  2. 如果 a 是 b 的码元前缀,则返回 true。

  3. 令 n 为最小的索引,使得 a 的第 n 个码元 不同于 b 的第 n 个码元。(这样的 索引必然存在,因为两个字符串均不是另一个的前缀。)

  4. 如果 a 的第 n 个码元小于 b 的第 n 个码元,则返回 true。

  5. 返回 false。

这与 JavaScript 的 < 运算符以及它在 字符串数组上的 sort() 方法所使用的排序一致。此排序比较每个 字符串中的 16 位码元,从而产生高效、一致且确定的排序顺序。所得 顺序不会与任何特定字母表或字典序一致,特别是对于 由代理项对表示的码点。[ECMA-262]

例如,码点 U+FF5E FULLWIDTH TILDE (~) 显然小于码点 U+1F600 (😀),但波浪号由单个 码元 0xFF5E 组成,而笑脸由两个码元 0xD83D 和 0XDE00 组成,因此笑脸 码元小于 波浪号。


在一个字符串 string 中,从 start 开始、长度为 length 的 码元 子字符串 按如下方式确定:

  1. 断言:start 和 length 均为非负数。

  2. 断言:start + length 小于或等于 string 的长度。

  3. 令 result 为空字符串。

  4. 对于范围 start 到 start + length(不含后者)中的每个 i: 将 string 的第 i 个 码元追加到 result。

  5. 返回 result。

在一个字符串 string 中,从 start 到 end 的码元子字符串,是 string 中从 start 开始、长度为 end − start 的码 元子字符串。

在一个字符串 string 中,从 start 到字符串末尾的码元子字符串,是 string 中从 start 到 string 的长度的 码元子字符串。

在 "Hello world" 中,从 1 开始、 长度为 3 的码元子字符串是 "ell"。这也可以表示为 从 1 到 4 的码元子字符串。

最好将提供给这些算法的数字理解为 码元之间的位置,而不是 码元本身的索引。返回的子字符串 随后由这些位置之间的码元构成。这解释了为什么,例如, 在空字符串中从 0 到 0 的码元子字符串 是空字符串,即使空字符串中索引 0 处不存在任何码元。

在一个字符串 string 中,从 start 开始、长度为 length 的码点 子字符串 按如下方式确定:

  1. 断言:start 和 length 均为非负数。

  2. 断言:start + length 小于或等于 string 的码点长度。

  3. 令 result 为空字符串。

  4. 对于范围 start 到 start + length(不含后者)中的每个 i: 将 string 的第 i 个 码点追加到 result。

  5. 返回 result。

在一个字符串 string 中,从 start 到 end 的码点子字符串,是 string 中从 start 开始、长度为 end − start 的码点 子字符串。

在一个字符串 string 中, 从 start 到字符串末尾的码点子字符串 是 string 中从 start 到 string 的码点长度的码点子字符串。

通常,当给定的是开发者提供的位置或 长度时,会使用码 元子字符串,因为这就是 JavaScript 中字符串索引的工作方式。例如,参见 CharacterData 类的方法。[DOM]

否则,码 点子字符串通常更合适。例如, 在 "👽" 中从 0 开始、长度为 1 的码点 子字符串是 "👽", 而在 "👽" 中从 0 开始、长度为 1 的码 元子字符串是 包含单个代理项 U+D83B 的字符串。


要对一个同构字符串 input 进行同构 编码:返回一个 字节序列,其长度 等于 input 的 码 点长度,且其字节具有 与 input 的码点的 值相同的值, 并保持相同顺序。


要对一个字符串进行ASCII 小写化,将 该字符串中的所有ASCII 大写字母 替换为其对应的ASCII 小写字母 码点。

要对一个字符串进行ASCII 大写化,将 该字符串中的所有ASCII 小写字母 替换为其对应的ASCII 大写字母 码点。

如果一个字符串 A 与 一个字符串 B 是ASCII 不区分大小写匹配,则 A 的 ASCII 小写化结果 与 B 的 ASCII 小写化结果 相同。

要对一个ASCII 字符串 input 进行ASCII 编码:返回 input 的同构 编码。

同构编码和UTF-8 编码 对 input 返回相同的字节 序列。

要对一个字节序列 input 进行ASCII 解码,执行以下步骤:

  1. 断言:input 中的所有字节都是ASCII 字节。

    注意:此前置条件确保对于此输入, 同构解码和 UTF-8 解码返回相同的字符串。

  2. 返回 input 的同构解码结果。


要从一个字符串中去除换行符,移除该字符串中的所有 U+000A LF 和 U+000D CR 码点。

要在一个字符串中规范化换行符,将每个 U+000D CR U+000A LF 码点对替换为单个 U+000A LF 码点,然后 将每个剩余的 U+000D CR 码点替换为 U+000A LF 码点。

要从 一个字符串中去除前导和尾随 ASCII 空白,移除所有 位于该字符串开头或末尾的 ASCII 空白。

要在一个字符串中去除 并折叠 ASCII 空白,将该字符串中 由一个或多个连续的、属于ASCII 空白的码 点组成的任何序列 替换为单个 U+0020 SPACE 码 点,然后从 该字符串中移除所有前导和尾随 ASCII 空白。


要从一个字符串 input 中,在给定用于跟踪调用算法在 input 中位置的 位置变量 position 的情况下,收集一系列满足条件 condition 的码点:

  1. 令 result 为空字符串。

  2. 当 position 未指向 input 末尾之后,并且 input 中 position 处的码点 满足条件 condition 时:

    1. 将该码 点追加到 result 末尾。

    2. 将 position 前进 1。

  3. 返回 result。

除了返回所收集的码点之外,此算法还会更新 调用算法中的位置 变量。

要在一个字符串 input 中,在给定位置 变量 position 的情况下,跳过 ASCII 空白,即从 input 中根据 position收集一系列码点,这些码点是 ASCII 空白。收集到的 码点不会被使用,但 position 仍会被更新。


要在特定分隔符码点 delimiter 处分割 字符串 input,即严格分割一个字符串:

  1. 令 position 为 input 的一个位置变量,最初 指向 input 的开头。

  2. 令 tokens 为一个由字符串组成的列表,初始为空。

  3. 令 token 为根据 position 从 input 中 收集一系列码点的结果,这些码点 不等于 delimiter。

  4. 将 token追加到 tokens。

  5. 当 position 未超过 input 的末尾时:

    1. 断言:input 中 position 处的码点是 delimiter。

    2. 将 position 前进 1。

    3. 令 token 为根据 position 从 input 中 收集一系列码点的结果,这些码点 不等于 delimiter。

    4. 将 token追加到 tokens。

  6. 返回 tokens。

此算法进行“严格”分割,与下面常用的 按 ASCII 空白分割和 按逗号分割变体不同,后两者 在处理夹杂的ASCII 空白时, 会以各种方式更加宽松。

要按 ASCII 空白分割 一个字符串 input:

  1. 令 position 为 input 的一个位置变量,最初 指向 input 的开头。

  2. 令 tokens 为一个由字符串组成的列表,初始为空。

  3. 根据 position 在 input 中跳过 ASCII 空白。

  4. 当 position 未超过 input 的末尾时:

    1. 令 token 为根据 position 从 input 中 收集一系列码点的结果,这些码点 不是ASCII 空白。

    2. 将 token追加到 tokens。

    3. 根据 position 在 input 中跳过 ASCII 空白。

  5. 返回 tokens。

要按逗号分割一个字符串 input:

  1. 令 position 为 input 的一个位置变量,最初 指向 input 的开头。

  2. 令 tokens 为一个由字符串组成的列表,初始为空。

  3. 当 position 未超过 input 的末尾时:

    1. 令 token 为根据 position 从 input 中 收集一系列码点的结果,这些码点 不是 U+002C (,)。

      token 可能为空字符串。

    2. 去除前导和尾随 ASCII 空白,作用于 token。
    3. 将 token追加到 tokens。

    4. 如果 position 未超过 input 的末尾:

      1. 断言:input 中 position 处的码点 是 U+002C (,)。

      2. 将 position 前进 1。

  4. 返回 tokens。

要使用可选的分隔符字符串 separator连接一个由 字符串组成的列表 list,执行 以下步骤:

  1. 如果 list为 空,则返回空字符串。

  2. 如果未给出 separator,则将 separator 设为空字符串。

  3. 返回一个字符串,其内容是 list 的条目,按 顺序排列,并由 separator 相互分隔。

要序列化一个集合 set,返回 使用 U+0020 SPACE 对 set 进行连接的结果。

4.8. 时间(Time)

表示时间时,使用 时刻(moment)和时间段(duration)规范类型。创建和与 JavaScript 交换这些类型时,请遵循 High Resolution Time § 3 规范作者工具中的建议。[HR-TIME]

4.9. 单例

以下类型可作为算法性描述的输入或输出,以提高理解度。它们不具有可观察的表现形式,仅通过影响特定算法流程,并且只能用“is”进行比较。

allowed

表示某个操作被允许,通常与blocked相对使用。

blocked

表示某个操作不被允许,通常与 allowed相对使用。

failure

表示某个操作未按预期进行,常与 success相对使用,或与更具体的类型组合使用。

success

表示某个操作按预期进行,常与 failure相对使用。

下列算法返回success 或failure:

  1. 如果发生了意外的坏情况,则返回failure。

  2. 返回success。

调用该算法的代码可能按如下方式检查结果:

  1. 如果result是failure,则优雅地关闭所有内容并中止。

  2. 断言:result是success。

  3. …

Failure经常作为能失败或返回值的算法结果:

  1. 如果status小于 200 或大于 599,则返回failure。

  2. 返回status。

5. 数据结构(Data structures)

传统上,规范常用一些语义共识的、但具体边界模糊的规范层数据结构。这通常效果良好,但在边界情况(如迭代顺序、或向有序集合追加其已包含的项时)会引发歧义。对更复杂数据结构(如映射)的记法和表达也因此不统一。

本标准定义了一小组通用数据结构及其操作与表述方式,以便规范间统一表述。

5.1. 列表(Lists)

一个列表是一种规范类型 ,由有限的有序 项序列组成。

为便于表示,可以使用字面量语法来表示列表,方法是用 U+00AB («) 和 U+00BB (») 将列表括起来,并使用 U+002C (,) 分隔其项。 可以通过在方括号中提供列表的从零开始的索引来使用索引语法。 索引不能越界,但与存在一起使用时除外。

令 example 为列表 « "a", "b", "c", "a" »。则 example[1] 是字符串 "b"。

为便于表示,可以使用多重赋值语法将多个变量 赋值为列表的项,方法是用 U+00AB («) 和 U+00BB (»)(与列表相同)将要赋值的变量括起来,并使用 U+002C (,) 分隔每个变量名。 列表的大小必须与要 赋值的变量数量相同。然后将每个 给定变量设为列表中对应索引处的项的 值。

  1. 令 value 为列表 « "a", "b", "c" »。

  2. 令 « a, b, c » 为 value。

  3. 断言:a 是 "a"。

  4. 断言:b 是 "b"。

  5. 断言:c 是 "c"。

当列表的内容并非完全受控时, 例如列表来自用户输入, 应检查列表的大小,以确保在 使用列表多重赋值语法之前 其大小符合预期。

  1. 如果 list 的大小 不是 3,则返回失败。

  2. 令 « a, b, c » 为 list。

给定列表 a 和 b,一个列表的相等性在 a 的 大小是 b 的大小,并且对于 a 的索引中的每个 i,a[i] 等于 b[i] 时返回 true;否则返回 false。


向一个不是有序集合的列表追加 ,就是将给定的项添加到 列表末尾。

要使用一个 列表 B扩展 一个不是有序集合的列表 A,对 B 的每个 item,将 item追加到 A。

  1. 令 ghostbusters 为 « "Erin Gilbert", "Abby Yates" »。

  2. 使用 « "Jillian Holtzmann", "Patty Tolan" »扩展 ghostbusters。

  3. 断言: ghostbusters 的大小是 4。

  4. 断言: ghostbusters[2] 是 "Jillian Holtzmann"。

向一个列表 (其不是有序集合)前置添加,就是将给定的项添加到 列表开头。

在一个不是有序集合的列表中替换, 就是将列表中所有符合给定条件的项替换为给定的项, 如果没有任何项符合条件,则不执行任何操作。

当列表是有序集合时,上述定义有所修改;参见 下文的有序集合追加、前置添加和 替换。

在一个 索引之前将一个项插入一个列表, 就是把给定项添加到给定索引 − 1 与给定索引之间。如果 给定索引为 0,则将给定项前置添加到列表。

要从一个列表中移除零个或多个项:

  1. 令 sizeBefore 为 list 的大小。

  2. 移除列表中所有符合给定条件的项,如果没有任何项符合条件,则不执行任何操作。

  3. 如果 list 的大小小于 sizeBefore,则返回 true;否则返回 false。

从 列表 « x, y, z, x » 中移除 x,就是移除所有 等于 x 的项。该列表现在等价于 « y, z »。

从 列表 « "a", "b", "ab", "ba" » 中移除所有以字符串 "a" 开头的项, 就是 移除项 "a" 和 "ab"。该列表现在等价于 « "b", "ba" »。

清空一个列表,就是移除 它的所有项。

如果一个列表的某个 项等于某个 项,则该列表包含该项。我们 也可以这样表示:对于一个列表 list 和索引 index, “list[index] 存在”。

一个列表的大小是该列表 包含的 项的数量。

如果一个列表的 大小为零,则该列表为空。

要获取一个 列表的索引,返回从 0 到该列表 大小(不含后者)的范围。

要迭代一个列表,按顺序对每个项 执行一组步骤, 使用形如 “对于每个 list 中的 item”的表述,然后在 后续文字中对 item 执行操作。

要对一个列表 list 进行切片,给定一个可选的 整数from(默认为 0)和一个可选的整数 to(默认为 list 的大小),执行 以下步骤。它们返回一个与 list 具有相同称谓的列表。

  1. 断言:from 大于或等于 0。

  2. 断言:to 小于 或等于 list 的大小。

  3. 断言:to 大于 或等于 from。

  4. 令 slice 为一个新的列表, 其称谓与 list 相同。

  5. 对于范围 from 到 to(不含后者)中的每个 i: 将 list[i]追加 到 slice。

  6. 返回 slice。

要检查一个列表 list是否有 重复项:

  1. 令 seenItems 为 « »。

  2. 对于 list 的每个 item:

    1. 如果 seenItems包含 item,则返回 true。

    2. 将 item追加到 seenItems。

  3. 返回 false。

克隆一个列表 list,就是返回 list 的一个 切片。

这是“浅克隆”,因为项本身并不会以 任何方式被克隆。

令 original 为有序 集合 « "a", "b", "c" »。克隆 original 会创建 一个新的有序集合 clone,因此在 clone 中将 "a"替换为 "foo" 会得到 « "foo", "b", "c" », 而 original[0] 仍然是字符串 "a"。

反转一个列表 list,就是创建一个新的 列表 reversed,其称谓相同,并且对于 list 的每个 item, 将 item前置添加到 reversed。

要使用一个小于 算法 lessThanAlgo 对一个列表 list按升序排序,就是创建一个新的列表 sorted,其中包含与 list 相同的项,但排序后根据 lessThanAlgo,每个项都小于其后面的项(如果有)。对于排序结果相同的项 (即 lessThanAlgo 对两个方向的比较都返回 false 的项),它们在 sorted 中的相对顺序必须与它们在 list 中的相对顺序相同。

要使用一个小于 算法 lessThanAlgo 对一个列表 list按 降序排序,就是创建一个新的列表 sorted,其中包含与 list 相同的项,但排序后根据 lessThanAlgo,每个项都小于其前面的项(如果有)。对于排序结果相同的项 (即 lessThanAlgo 对两个方向的比较都返回 false 的项),它们在 sorted 中的相对顺序必须与它们在 list 中的相对顺序相同。

令 original 为列表 « (200, "OK"), (404, "Not Found"), (null, "OK") »。如果在 a 的第二个项 码元小于 b 的第二个项时将 a 视为小于 b,则对 original排序 为 升序会得到结果 « (404, "Not Found"), (200, "OK"), (null, "OK") »。


列表类型源自 JavaScript 规范(其中首字母大写,写作 List);为便于参考,我们在这里重复其定义中的一些内容, 并提供用于操作列表的扩展词汇。每当 JavaScript 需要一个 List 时,都可以使用此处定义的列表;它们是同一种类型。 [ECMA-262]

5.1.1. 栈(Stacks)

一些列表被指定为栈。栈是 一个列表, 但按照惯例,会使用以下操作来操作它,而不是使用 追加、前置添加或移除。

向一个栈压入 ,就是向其追加。

从一个栈弹出 :如果该栈 不为空,则移除其最后一个项并返回 它;否则,什么也不返回。

查看 一个栈:如果该栈 不为空,则返回其 最后一个项;否则,什么也 不返回。

虽然栈是列表,但不得对其使用对于每个; 相反,组合使用当和弹出更为合适。

5.1.2. 队列(Queues)

一些列表被指定为队列。队列是 一个列表, 但按照惯例,会使用以下操作来操作它,而不是使用 追加、前置添加或移除。

在一个队列中入队 ,就是向其追加。

从一个队列出队,就是移除其第一个 项并返回它,如果该队列不为空;如果为空,则 什么也不返回。

虽然队列是列表,但不得对其使用对于每个; 相反,组合使用当和出队更为合适。

5.1.3. 集合(Sets)

一些列表被指定为有序 集合。一个 有序集合是一个列表,并具有额外的 语义,即它不得包含两个 项,且它们彼此相等。

Web 平台上的几乎所有情况都需要一个有序集合,而不是 无序集合,因为互操作性要求任何向开发者公开的集合内容枚举在不同浏览器之间 保持一致。在那些顺序并不重要的情况下,我们仍然使用 有序集合;实现可以根据顺序不可观察这一事实进行优化。

给定一个列表 input,要创建一个集合:

  1. 令 result 为空集合。

  2. 对于 input 的每个 item,将 item追加到 result。

  3. 返回 result。

向一个有序集合追加:如果该集合包含 给定的项,则不执行任何操作; 否则,执行通常的列表 追加操作。

要使用一个列表 B扩展 一个有序集合 A,对于 B 的每个 item,将 item追加到 A。

向一个有序集合前置添加:如果该集合 包含给定的项,则不执行任何操作;否则, 执行 通常的列表前置添加操作。

给定 item 和 replacement,要在一个有序集合 set 中替换:如果 set 包含 item 或 replacement,则将两者中首次出现的那个替换为 replacement,并移除其他所有实例。

在 有序集合 « "a", "b", "c" » 中将 "a" 替换为 "c" 会得到 « "c", "b" »。在 « "c", "b", "a" » 中也会得到 « "c", "b" »。

如果对于有序集合 set 的 每个 item, 另一个有序集合 superset包含 item,则 set 是 superset 的 子集(反之,superset 是 set 的超集)。

这意味着一个有序集合既是它自身的子集,也是 它自身的超集。

如果一个集合 A 是 B 的子集,并且 A 是 B 的超集,则 A 与一个 集合 B无序相等。

有序集合 « 1, 2 » 和 « 2, 1 » 是 无序相等的, 但它们并不相等,因为 列表的相等性会考虑其 项的顺序。

有序集合 A 和 B 的交集,是创建一个新的有序集合 set,并且对于 A 的每个 item,如果 B 包含 item,则将 item追加到 set 后所得的结果。

有序集合 A 和 B 的并集,是 将 A克隆为 set,并且对于 B 的每个 item, 将 item追加到 set 后所得的结果。

有序集合 A 和 B 的差集,是创建一个新的有序集合 set,并且对于 A 的每个 item,如果 B 不包含 item,则将 item追加 到 set 后所得的结果。


从 n 到 m(含)的范围, 会创建一个新的有序 集合,其中包含从 n 开始到 m(含)的所有整数,并按连续递增顺序排列,前提是 m 大于 或等于 n。

从 n 到 m(不含)的范围, 会创建一个新的有序集合 ,其中包含从 n 开始直到并包括 m − 1 的所有整数,并按连续递增顺序排列,前提是 m 大于 n。如果 m 等于 n,则会创建一个空的有序集合。

对于范围 1 到 4(含)中的每个 n,…

5.2. 映射(Maps)

一个有序映射,有时简称为“映射”,是一种 由有限有序元组序列组成的规范类型,每个元组 由一个键和 一个值组成, 且任意两个键都不 相等。每个这样的元组称为一个条目。

与有序集合一样,默认情况下,我们假定映射需要保持有序,以实现 各实现之间的互操作性。

可以使用字面量语法表示有序映射,方法是以 U+00AB («) 和 U+005B ([) 开始一个有序映射,以 U+005D (]) 和 U+00BB (») 结束,并使用 U+2192 (→) 分隔每个条目的 key 和 value,并使用 U+002C (,) 分隔其条目。

令 example 为有序 映射 «[ "a" → `x`, "b" → `y` ]»。则 example["a"] 是字节序列 `x`。

给定有序映射 a 和 b,一个有序映射的相等性在以下情况下返回 true: a 的大小与 b 的大小相同,并且 a 的每个条目都有一个 键和一个值,它们分别等于 b 中相同位置的键 和 值,该键和值属于 b 中相同位置的条目;否则返回 false。


给定一个键 key 和一个可选的 default,要获取条目的值,该条目位于一个 有序映射 map 中:

  1. 如果 map 不包含 key,且给出了 default ,则返回 default。

  2. 断言:map包含 key。

  3. 返回 map 中键等于 key 的条目的值。

我们也可以使用索引语法表示获取条目的 值, 即在一个映射之后紧接着的方括号中提供一个键。 可以通过添加短语默认值为,后跟 默认值来给出默认值。

如果 map["test"] 存在,则返回 map["test"]。

令 example 为有序映射 «[ "a" → "x", "b" → "y" ]»。则 example["a"] 与 example["a"]默认值为 "z" 相同, 即 "x"。example["c"] 会触发断言。 example["c"]默认值为 "z" 时是 "z"。

在一个有序映射中,将条目的值设为给定的值,就是在映射包含具有给定键的 现有条目时更新其值, 或者如果不存在这样的条目,则将具有给定键/值的新条目添加到映射末尾。我们也可以 这样表示:对于一个有序映射 map、键 key 和值 value, “将 map[key]设为 value”。

从一个有序映射中移除条目,就是从该映射中移除 所有符合给定条件的条目,如果没有任何条目符合条件,则不执行任何操作。如果 条件是具有某个键, 那么我们也可以这样表示:对于 一个有序映射 map 和 键 key,“移除 map[key]”。

清空一个 有序映射,就是从该映射中移除所有条目。

如果存在一个条目,其键 等于给定键,则一个有序映射包含一个 具有给定键的条目。我们也可以 这样表示:对于一个有序映射 map 和键 key,“map[key] 存在”。

要获取一个 有序映射的键,返回一个新的有序集合,其项分别是该映射 条目中的每个键。

要获取一个 有序映射的值,返回一个新的列表,其项分别是该 映射的条目中的每个值。

一个有序映射的大小,是 对该映射执行获取 键后所得结果的大小。

如果一个有序映射的 大小为零,则该有序映射为空。

要迭代一个有序映射,按顺序对每个条目 执行一组步骤,使用形如 “对于 map 的每个 key → value”的表述,然后在 后续文字中对 key 和 value 执行操作。

克隆一个 有序映射 map,就是 创建一个新的 有序映射 clone, 并且对于 map 的每个 key → value, 将 clone[key]设为 value。

这是“浅克隆”,因为键和值 本身不会以任何方式被克隆。

令 original 为有序 映射 «[ "a" → «1, 2, 3», "b" → «» ]»。克隆 original 会创建一个 新的有序映射 clone, 因此将 clone["a"]设为 «-1, -2, -3» 会得到 «[ "a" → «-1, -2, -3», "b" → «» ]»,并使 original 保持不变。但是,向 clone["b"]追加 4 会修改 clone 和 original 中对应的值,因为它们都指向 同一个列表。

要使用一个小于 算法 lessThanAlgo 对一个映射 map按升序排序,就是创建一个新的映射 sorted,其中包含与 map 相同的条目,但排序后根据 lessThanAlgo,每个条目都小于其后面的条目(如果有)。对于排序结果 相同的条目 (即 lessThanAlgo 对两个方向的比较都返回 false 的条目),它们在 sorted 中的相对顺序必须与它们在 map 中的相对顺序相同。

要使用一个小于 算法 lessThanAlgo 对一个映射 map按 降序排序,就是创建一个新的映射 sorted,其中包含与 map 相同的条目,但排序后根据 lessThanAlgo,每个条目都小于其前面的条目(如果有)。对于排序结果 相同的条目 (即 lessThanAlgo 对两个方向的比较都返回 false 的条目),它们在 sorted 中的相对顺序必须与它们在 map 中的相对顺序相同。

5.3. 结构体(Structs)

一个结构体是一种规范类型 ,由有限的 项集合组成,其中每一项都有一个唯一且不可变的 名称。一个项持有一个已定义类型的值。

电子邮件是一个示例结构体,由 本地部分(一个字符串)和一个主机(一个主机)组成。

一个无实际意义的算法可能会按如下方式使用此定义:

  1. 令 email 为一封电子邮件,其本地部分是 "hostmaster",且主机 是 infra.example。
  2. …

除非其定义另有规定,一个不是元组的结构体只 等于其自身:如果两个结构体彼此 相同,则其相等性返回 true;否则返回 false。

给定结构体 a 和 b,逐项相等在 a 的每个项 等于 b 中 具有相同名称的项时返回 true;否则返回 false。一个结构体的定义可以规定 其相等性为逐项相等。

使用上面的电子邮件定义:

  1. 令 email 为一封电子邮件,其本地部分是 "hostmaster",且主机是 infra.example。

  2. 令 sameEmail 为 email。

  3. 令 otherEmail 为一封电子邮件,其本地部分是 "hostmaster",且主机是 infra.example。

  4. 断言:email等于 sameEmail,因为 它们彼此 相同。

  5. 断言:email 不等于 otherEmail, 因为它们彼此并不 相同。

如果电子邮件的定义改为规定电子邮件的相等性为 逐项 相等,则 email 会等于 otherEmail,因为它们的本地 部分彼此相同,并且它们的 主机相等。

因此,如果一个结构体 的定义未规定其相等性,而它被用作 一个有序映射的键,或一个有序集合的项,那么只能使用完全相同的那个结构体再次找到它 。如果这不是所期望的, 则其定义需要规定其相等性,例如规定为逐项相等。

5.3.1. 元组(Tuples)

一个元组是一个结构体,其项是有序的。为 便于表示,可以使用字面量语法来表示元组,方法是用圆括号括住 元组,并使用 U+002C (,) 分隔其项。要使用这种 表示法,名称需要从 上下文中明确得知。这可以通过在 第一个实例之前加上为元组指定的名称来实现。可以通过 在方括号中提供元组的从零开始的索引来使用索引语法 。索引不能 越界。

状态是一个示例元组,由一个代码(一个 数字)和文本(一个字节序列)组成。

一个为了演示状态元组用法而操作它们的无实际意义的算法 如下:

  1. 令 statusInstance 为状态 (200, `OK`)。
  2. 将 statusInstance 设为 (301, `FOO BAR`)。
  3. 如果 statusInstance 的 code 是 404,则 …

最后一步也可以写成“如果 statusInstance[0] 是 404,则 …”。如果 元组的名称没有显式 定义,这种写法可能更合适。

并非所有结构体都是元组,这是有意为之。使用 Infra 标准的文档可能需要灵活地向其结构体添加新的名称, 而不会破坏其依赖项所使用的字面量语法。在这种情况下,元组并不合适。

一个元组的相等性是逐项相等,除非 其定义 另有规定。

将一个元组更改为 一个不是元组的结构体,也意味着它 不再具有逐项相等,除非随后在其定义中明确规定这一点。

6. JSON

本节算法所使用的约定与 JavaScript 规范中的约定一致。[ECMA-262]

给定一个字符串 string,要将 JSON 字符串解析为 JavaScript 值:

  1. 返回 ? Call(%JSON.parse%, undefined, « string »)。

给定一个字节序列 bytes,要 将 JSON 字节解析为 JavaScript 值:

  1. 令 string 为对 bytes 执行UTF-8 解码所得的结果。[ENCODING]

  2. 返回给定 string将 JSON 字符串解析为 JavaScript 值所得的结果。

给定一个 JavaScript 值 value,要将 JavaScript 值序列化为 JSON 字符串:

  1. 令 result 为 ? Call(%JSON.stringify%, undefined, « value »)。

    由于没有向%JSON.stringify%传递额外参数,因此所得 字符串不会插入任何空白。

  2. 如果 result 是 undefined,则抛出一个 TypeError。

    当 value 没有 JSON 表示形式时可能发生这种情况, 例如, 当它是 undefined 或函数时。

  3. 断言:result 是一个 字符串。

  4. 返回 result。

给定一个 JavaScript 值 value,要 将 JavaScript 值序列化为 JSON 字节:

  1. 令 string 为给定 value将 JavaScript 值序列化为 JSON 字符串所得的结果。

  2. 返回对 string 执行UTF-8 编码所得的结果。[ENCODING]


上述操作直接作用于 JavaScript 值;具体而言,这意味着 所涉及的对象或数组与特定的JavaScript 领域相关联。在 标准中,在 JSON 与不依赖领域的映射、 列表、字符串、布尔值、数字和 null 之间进行转换通常更方便。

给定一个字符串 string,要 将 JSON 字符串解析为 Infra 值:

  1. 令 jsValue 为 ? Call(%JSON.parse%, undefined, « string »)。

  2. 返回给定 jsValue将 JSON 派生的 JavaScript 值转换为 Infra 值所得的结果。

给定一个字节序列 bytes,要将 JSON 字节解析为 Infra 值:

  1. 令 string 为对 bytes 执行UTF-8 解码所得的结果。[ENCODING]

  2. 返回给定 string将 JSON 字符串解析为 Infra 值所得的结果。

给定一个 JavaScript 值 jsValue,要将 JSON 派生的 JavaScript 值转换为 Infra 值:

  1. 如果 jsValue 是 null、jsValue是 Boolean、jsValue 是 String,或者 jsValue是 Number,则返回 jsValue。

  2. 如果 IsArray(jsValue) 是 true:

    1. 令 result 为空列表。

    2. 令 length 为 ! ToLength(! Get(jsValue, "length"))。

    3. 对于范围 0 到 length − 1(含)中的每个 index:

      1. 令 indexName 为 ! ToString(index)。

      2. 令 jsValueAtIndex 为 ! Get(jsValue, indexName)。

      3. 令 infraValueAtIndex 为给定 jsValueAtIndex将 JSON 派生的 JavaScript 值转换为 Infra 值所得的结果。

      4. 将 infraValueAtIndex追加到 result。

    4. 返回 result。

  3. 令 result 为空有序映射。

  4. 对于 ! jsValue.[[OwnPropertyKeys]]() 的每个 key:

    1. 令 jsValueAtKey 为 ! Get(jsValue, key)。

    2. 令 infraValueAtKey 为给定 jsValueAtKey将 JSON 派生的 JavaScript 值转换为 Infra 值所得的结果。

    3. 将 result[key]设为 infraValueAtKey。

  5. 返回 result。

给定一个字符串、 布尔值、数字、null、列表,或者以字符串为键的映射 value,要将 Infra 值序列化为 JSON 字符串:

  1. 令 jsValue 为给定 value 将 Infra 值转换为 JSON 兼容的 JavaScript 值所得的结果。

  2. 返回 ! Call(%JSON.stringify%, undefined, « jsValue »)。

    由于没有向%JSON.stringify%传递额外参数,因此所得 字符串不会插入任何空白。

给定一个字符串、 布尔值、数字、null、列表,或者以字符串为键的映射 value,要将 Infra 值序列化为 JSON 字节:

  1. 令 string 为给定 value将 Infra 值序列化为 JSON 字符串所得的结果。

  2. 返回对 string 执行UTF-8 编码所得的结果。[ENCODING]

给定 value,要将 Infra 值转换为 JSON 兼容的 JavaScript 值:

  1. 如果 value 是字符串、 布尔值、数字或 null,则 返回 value。

  2. 如果 value 是一个列表:

    1. 令 jsValue 为 ! ArrayCreate(0)。

    2. 令 i 为 0。

    3. 对于 value 的每个 listItem:

      1. 令 listItemJSValue 为给定 listItem将 Infra 值转换为 JSON 兼容的 JavaScript 值所得的结果。

      2. 执行 ! CreateDataPropertyOrThrow(jsValue, ! ToString(i), listItemJSValue)。

      3. 将 i 设为 i + 1。

    4. 返回 jsValue。

  3. 断言:value 是一个映射。

  4. 令 jsValue 为 ! OrdinaryObjectCreate(null)。

  5. 对于 value 的每个 mapKey → mapValue:

    1. 断言: mapKey 是一个字符串。

    2. 令 mapValueJSValue 为给定 mapValue 将 Infra 值转换为 JSON 兼容的 JavaScript 值所得的结果。

    3. 执行 ! CreateDataPropertyOrThrow(jsValue, mapKey, mapValueJSValue)。

  6. 返回 jsValue。

由于在规范中直接操作 JavaScript 值很少是合适的,因此应优先使用将 Infra 值序列化为 JSON 字符串或 将 Infra 值序列化为 JSON 字节 ,而不是使用此算法。如果 你认为需要使用将 Infra 值转换为 JSON 兼容的 JavaScript 值,请提交 issue以讨论你的用例。

7. 宽容 base64(Forgiving base64)

要宽容 base64 编码(forgiving-base64 encode)给定字节序列 data,对 data 应用 RFC 4648 第 4 节定义的 base64 算法并返回结果。[RFC4648]

该操作之所以命名为 宽容 base64 编码,是为与宽容 base64 解码对称;后者与 RFC 有区别,定义了某些输入的错误处理。

要宽容 base64 解码(forgiving-base64 decode)给定字符串 data,执行如下步骤:

  1. 移除 data 中所有ASCII 空白符。

  2. 若 data 的码点长度能被 4 整除:

    1. 如 data 以一个或两个 U+003D (=) 码点结尾,则将其移除。

  3. 若 data 的码点长度除以 4 余 1,则返回失败。

  4. 若 data 包含不属于下列之一的码点:

    则返回失败。

  5. 令 output 为空字节序列。

  6. 令 buffer 为可追加比特的空缓冲区。

  7. 令 position 为 data 的位置变量,初始指向开头。

  8. 当 position 未越过 data 结尾时,重复:

    1. 在 RFC 4648 Table 1: The Base 64 Alphabet 的第二列中找到 position 指向的码点。令 n 为该行第一格的数字。[RFC4648]

    2. 将 n 的六个比特(高位在前)追加到 buffer。

    3. 如 buffer 已积累 24 比特,则将其解释为 3 个 8 位大端整数。将这 3 个字节依次追加到 output,然后清空 buffer。

    4. position 加 1。

  9. 如 buffer 非空,其长度为 12 或 18 比特。若为 12 比特,丢弃后 4 位,余下 8 位按大端解释为一个 8 位整数;若为 18 比特,丢弃后 2 位,余下 16 位按大端解释为两个 8 位整数。将所得 1 或 2 字节依次追加到 output。

    丢弃比特意味着如 "YQ" 和 "YR" 都还原为 `a`。

  10. 返回 output。

8. 命名空间(Namespaces)

HTML 命名空间为 "http://www.w3.org/1999/xhtml"。

MathML 命名空间为 "http://www.w3.org/1998/Math/MathML"。

SVG 命名空间为 "http://www.w3.org/2000/svg"。

XLink 命名空间为 "http://www.w3.org/1999/xlink"。

XML 命名空间为 "http://www.w3.org/XML/1998/namespace"。

XMLNS 命名空间为 "http://www.w3.org/2000/xmlns/"。

致谢(Acknowledgments)

特别感谢 Addison Phillips, Andreu Botella, Aryeh Gregor, Ben Kelly, Chris Rebert, Daniel Ehrenberg, Dominic Farolino, Gabriel Pivovarov, Ian Hickson, Jakob Ackermann, Jake Archibald, Jeff Hodges, Jeffrey Yasskin, Jungkee Song, Leonid Vasilyev, Maciej Stachowiak, Malika Aubakirova, Martin Thomson, Michael™ Smith, Mike West, Mike Taylor, Ms2ger, Noam Rosenthal, Pavel "Al Arz" Kurochkin, Philip Jägenstedt, Rashaun "Snuggs" Stovall, Sergey Shekyan, Simon Pieters, Tab Atkins, Tobie Langel, triple-underscore, Wolf Lammen, 以及薛富侨 的支持!

本标准由 Anne van Kesteren(Apple, annevk@annevk.nl)和 Domenic Denicola(Google, d@domenic.me)编写。

知识产权声明(Intellectual property rights)

版权所有 © WHATWG (Apple, Google, Mozilla, Microsoft)。本作品采用 知识共享署名 4.0 国际许可协议。如本标准部分内容被纳入源代码,则该部分按 BSD 3-Clause License 许可。

本标准为 Living Standard。如需专利审查版本,请参阅 Living Standard Review Draft。

索引(Index)

本规范定义的术语(Terms defined by this specification)

引用自其他规范的术语(Terms defined by reference)

参考文献(References)

规范性引用(Normative References)

[ECMA-262]
ECMAScript 语言规范。URL:https://tc39.es/ecma262/multipage/
[ENCODING]
Anne van Kesteren。编码标准。现行 标准。URL:https://encoding.spec.whatwg.org/
[HR-TIME]
Yoav Weiss。高精度时间。URL:https://w3c.github.io/hr-time/
[Infra]
Anne van Kesteren;Domenic Denicola。基础设施 标准。现行标准。URL:https://infra.spec.whatwg.org/
[RFC20]
V.G. Cerf。用于网络 交换的 ASCII 格式。1969 年 10 月。互联网标准。URL:https://www.rfc-editor.org/info/rfc20/
[RFC2119]
S. Bradner。用于 RFC 中 表示要求级别的关键词。1997 年 3 月。最佳当前实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[RFC4648]
S. Josefsson。Base16、Base32 和 Base64 数据 编码。2006 年 10 月。建议标准。URL:https://www.rfc-editor.org/info/rfc4648/
[UNICODE]
Unicode 标准。URL:https://www.unicode.org/versions/latest/
[WEBIDL]
Edgar Chen;Timothy Gu。Web IDL 标准。现行 标准。URL:https://webidl.spec.whatwg.org/

补充性参考文献

[COOKIES]
A. Barth。HTTP 状态管理机制。 2011 年 4 月。建议标准。URL:https://httpwg.org/specs/rfc6265.html
[DOM]
Anne van Kesteren。DOM 标准。现行标准。 URL:https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren。Fetch 标准。现行 标准。URL:https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren;等。HTML 标准。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[RFC6797]
J. Hodges;C. Jackson;A. Barth。HTTP 严格 传输安全(HSTS)。2012 年 11 月。建议标准。URL:https://www.rfc-editor.org/info/rfc6797/
[RFC791]
J. Postel。互联网协议。1981 年 9 月。互联网标准。URL:https://www.rfc-editor.org/info/rfc791/
[RFC8174]
B. Leiba。RFC 2119 关键词中大写与小写的 歧义。2017 年 5 月。最佳当前实践。URL:https://www.rfc-editor.org/info/rfc8174/
[RFC8259]
T. Bray,编。JavaScript 对象表示法(JSON) 数据交换格式。2017 年 12 月。互联网标准。URL:https://www.rfc-editor.org/info/rfc8259/
[STORAGE]
Anne van Kesteren。存储标准。现行 标准。URL:https://storage.spec.whatwg.org/
[URL]
Anne van Kesteren。URL 标准。现行标准。 URL:https://url.spec.whatwg.org/
[XML]
Tim Bray;等。可扩展标记语言(XML)1.0(第五 版)。2008 年 11 月 26 日。推荐标准。URL:https://www.w3.org/TR/xml/