目标
-
消除标准中的重复样板内容。
-
在标准之间对约定、术语和数据结构达成一致。
-
作为多个标准共用但尚无归属的概念的归宿。
-
通过澄清本可能含糊的概念,帮助撰写清晰易读的算法性文本。
欢迎提出更多目标建议。
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 绘制文本效果(如加边框等)都必不可少,但这也可能泄露可用于分组请求的信息(例如通过暴力枚举推测用户安装了哪些字体,而字体信息因人而异)。
在使用 Infra 标准的文档中,能被用作跟踪向量的功能会像本段一样标注。
平台上的其他特性也可能被用于同样目的,包括但不限于:
- 用户代理支持的功能列表。
- 脚本递归最大允许栈深度。
- 描述用户环境的功能。
- 用户的时区。
- HTTP 请求头。
3. 算法
3.1. 一致性
算法及其步骤中以祈使句表达的要求(如“去除所有前导空格”或“返回 false”),应根据引入该算法或步骤时所用关键词(如“must”)来理解其含义。如果未使用此类关键词,则默认视为 must。
例如,若规范写道:
要吃一个橙子,用户必须:
- 剥橙子的皮。
- 将橙子分成一片片。
- 吃掉橙子片。
这等价于:
要吃一个橙子:
- 用户必须剥橙子的皮。
- 用户必须将橙子分成一片片。
- 用户必须吃掉橙子片。
这里的关键词是“must”。
如果上述算法仅以“要吃一个橙子:”引入,含义不变,因默认即为 must。
以算法或具体步骤表达的一致性要求,可以以任何方式实现,只要最终结果等价即可。(这些算法本意是易于理解,而非高性能。)
性能很难做到绝对正确,因为它受用户感知、计算机架构以及随时间变化的输入类型影响。例如,JavaScript 引擎通常会针对标准化为单一算法的内容实现多种代码路径,以优化速度或内存消耗。将所有这些代码路径标准化几乎不可能,也无益于标准的长期生命力,因此性能应作为业界竞争领域。
3.2. 避免对算法输入的限制
使用 Infra 标准的文档通常不应对算法输入的大小、资源使用等施加具体限制。这有助于用户代理间的竞争,也避免限制未来的计算需求。
但用户代理可对本应不受约束的输入施加实现自定义限制。例如防止拒绝服务攻击、防止内存耗尽或绕过平台特定限制等。
全局资源限制可被用作资源耗尽攻击的侧信道,攻击者可通过观察受害应用是否达到全局限制来推断。若资源限制因硬件差异而显著不同,也可用于指纹识别用户代理。
一个允许创建内存位图的 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):
- ……
- 如果给定 navigationType,则对其进行相关操作。
- ……
调用带可选位置参数的算法时,可省略尾部可选参数。
对上述算法的调用形式如:
但无法仅为第三个(exceptionsEnabled)参数赋非默认值而不传第二个(navigationType);且最后一种调用对读者不直观,因为需要追溯参数顺序。请继续阅读下文解决办法!
可选命名参数(非位置参数)可提升调用处的清晰度和灵活性。这类参数应同时标记为变量和定义,并在调用处链接。
要导航至资源 resource,可选字符串 navigationType,可选布尔 exceptionsEnabled(默认为 false):
- ……
- 如给定 navigationType,则对其进行相关操作。
- ……
调用形式如:
- 导航至 resource。
- 导航至 resource,并将 navigationType 设为
"
form-submission
"。 - 导航至 resource,并将 exceptionsEnabled 设为 true。
- 导航至 resource,navigationType 设为
"
form-submission
",exceptionsEnabled 设为 true。
注意,在算法步骤中,参数值只是变量引用,不链接到参数声明;只有调用处才链接参数声明。
非可选的命名参数同样可以用上述方式标记并在调用处链接,有助于提升调用处的清晰度。
布尔参数无论是否可选,均建议使用命名参数,详见 The Pitfalls of Boolean Trap。
另一种提升清晰度的方法是将相关值打包为结构体(struct),并作为参数传递,尤其适用于多算法共用同一组相关值的场景。
3.5. 变量
变量使用 “let” 声明,使用 “set” 修改。
令 list 为新的列表。
令 activationTarget 为 isActivationEvent 为 true 且 target 具有激活行为时的 target,否则为 null。
变量在声明前不得使用。变量具有块作用域。每个算法中变量不得重复声明。
可用多重赋值语法,将多个变量赋值为元组的各项,即变量名用括号包裹并用逗号分隔。变量个数必须与元组的项数一致。
3.6. 控制流
算法的控制流中,"return" 或 "throw" 的要求会终止当前语句所在的算法。"return" 可将给定值返回给调用者。"throw" 会让调用者自动重新抛出给定值,并终止调用者算法。调用者可用叙述捕获异常并执行其他操作。
3.7. 条件中止
有时需要在某个条件变为真时停止执行一系列步骤。
此时,应声明该系列步骤将在特定 condition 满足时中止(abort when)。这表明这些步骤应在每一步前插入一条判断 condition 的语句,若其为真,则跳过剩余步骤。
若算法如此设计,则后续步骤可标注为中止时(if aborted),即若前面某步因 abort when 条件为真而被跳过,则该步必须执行。
使用该结构时,实现允许在指定步骤期间而非每步前后检查 condition,只要最终结果不变即可。例如,上述例子中只要 result 在运算期间未被修改,用户代理可中断计算。
3.8. 条件语句
带条件语句的算法应使用 "if"、"then" 和 "otherwise" 关键词。
-
令 value 为 null。
-
如果 input 是字符串,则将 value 设为 input。
-
返回 value。
一旦使用 "otherwise",就不再用 "then"。
-
令 value 为 null。
-
如果 input 是字符串,则将 value 设为 input。
-
否则,将 value 设为失败。
-
返回 value。
3.9. 循环
可用多种方式重复执行一组步骤直到满足某条件。
Infra 标准尚未穷尽所有迭代写法,如有需要请提 issue。
迭代流程可用 continue(继续)或 break(中断)控制。continue 会跳过本次迭代剩余步骤进入下一个元素,如无剩余元素则终止。break 则跳过本次及后续所有元素,终止迭代。
令 example 为列表 « 1, 2, 3, 4 »。下述叙述会对 1、2、3、4 依次执行 operation:
-
对于每个 item 属于 example:
- 对 item 执行 operation。
下述叙述会对 1、2、4 执行 operation,跳过 3。
下述叙述会对 1、2 执行 operation,跳过 3、4。
3.10. 断言
为提升可读性,可在算法中添加断言,用于表达不变量。写法为“断言:”,后接必须为真的语句。若该语句为假,则表明使用 Infra 标准的文档存在问题,应予以报告和修正。
因断言永远为真,对实现无要求。
-
令 x 为 "
Aperture Science
"。 -
断言:x 等于 "
Aperture Science
"。
4. 基本数据类型
4.1. 空值(Nulls)
值 null 用于表示没有值。它可与 JavaScript 的 null 值互换使用。[ECMA-262]
4.2. 布尔值(Booleans)
布尔值只能为 true 或 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)就是其底层数字值。
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(空格)到 0x7E(~)范围内,也可写作字符串,但需用反引号而非引号,避免与实际字符串混淆。
若需将字符串转为字节序列,建议使用 Encoding 中的UTF-8 编码;极少数情况下可用同构编码。[ENCODING]
要字节序列转小写(byte-lowercase),把字节序列中范围 0x41(A)到 0x5A(Z)的每个字节加 0x20。
要字节序列转大写(byte-uppercase),把字节序列中范围 0x61(a)到 0x7A(z)的每个字节减 0x20。
如字节序列 A 的字节序列转小写与 B 的字节序列转小写一致,则 A 与 B 为大小写不敏感(byte-case-insensitive)匹配。
若下述步骤返回 true,则字节序列potentialPrefix为字节序列input的前缀(prefix):
-
令 i 为 0。
-
当 true:
"input 以……为前缀(starts with) potentialPrefix" 可作为 "potentialPrefix 是 input 的前缀" 的同义写法。
字节序列 a 小于(byte less than) 字节序列 b,当且仅当如下步骤返回 true:
-
如 b 是 a 的前缀,返回 false。
-
如 a 是 b 的前缀,返回 true。
-
令 n 为 a 与 b 第一个不同字节的最小索引。(必有此索引,因为两者互不为前缀。)
-
如 a 的第 n 个字节小于 b 的第 n 个字节,返回 true。
-
返回 false。
4.6. 码点(Code points)
码点指 Unicode 码点,表示为 "U+" 后跟四到六个ASCII 大写十六进制数字,范围是 U+0000 到 U+10FFFF(包括端点)。码点的值即其底层数字。
码点后可以跟其名称、在括号中的显示形式(如不是 U+0028 或 U+0029),或两者都跟。建议使用 Infra 标准的文档,在无法显示或为 U+0028/U+0029 时用其名称,否则为提升可读性用括号内的显示形式。
码点的名称定义在 Unicode 中,用ASCII 大写字母表示。[UNICODE]
难以明确显示的码点,如 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(包括端点)的码点。
非字符(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 小写字母。
4.7. 字符串(Strings)
字符串是一组16 位无符号整数,也称为码元(code unit)。 字符串也称为JavaScript 字符串。字符串使用双引号与等宽字体表示。
这与Unicode中“码元”的定义不同,本规范特指 Unicode 16 位字符串的定义。[UNICODE]
字符串也可按 JavaScript 规范 The String Type 一节的定义,解释为包含码点。[ECMA-262]
该转换过程会将代理对(surrogate pair)转换为对应的标量值,并将剩余的代理项映射为对应码点,即保持原样。
由码元 0xD83D、0xDCA9 和 0xD800 组成的字符串,按码点解释后为 U+1F4A9 和 U+D800 两个码点。
字符串的码点长度(code point length)是其包含的码点数。
为表达字符串对所含码点的特殊限制,本规范定义了ASCII 字符串、同构字符串和标量值字符串。在规范中使用这些类型有助于提升清晰性。
同构字符串是所有码点均在 U+0000 NULL 至 U+00FF(ÿ)范围内的字符串。
标量值字符串适用于所有涉及 I/O 或需要UTF-8 编码的操作。
要将 字符串 转换为 标量值字符串,需将其中的任何 代理项(surrogates) 替换为 U+FFFD(�)。
字符串 a 等于(is/identical to) 字符串 b,当且仅当二者码元序列完全一致。
除非另有说明,所有字符串比较均用is。
这种字符串比较方式在 HTML 中曾称为“区分大小写”比较。is 比较不仅区分大小写,还区分码点的各种编码方式,如规范化形式或组合符顺序。即使在 Unicode 意义下视觉或规范等价的两字符串也可能不is。
字符串 potentialPrefix 是码元前缀(code unit prefix),当且仅当如下步骤返回 true:
-
令 i 为 0。
-
当 true:
若上下文明确是码元(如字符串字面量只含 U+0020 SPACE 到 U+007E (~)),则 "input 以……为前缀(starts with) potentialPrefix" 可视为 "potentialPrefix 为 input 的码元前缀" 的同义说法。
若取值未知,建议显式表达:targetString 是 userInput 的码元前缀。若为字面量,则可直接说:userInput 以 "!
" 开头。
字符串 potentialSuffix 是码元后缀(code unit suffix),当且仅当如下步骤返回 true:
-
令 i 为 1。
-
当 true:
-
令 potentialSuffixIndex 为 potentialSuffix 的长度 − i。
-
令 inputIndex 为 input 的长度 − i。
-
如 potentialSuffixIndex < 0,则返回 true。
-
如 inputIndex < 0,则返回 false。
-
令 potentialSuffixCodeUnit 为 potentialSuffixIndex 处的码元。
-
令 inputCodeUnit 为 inputIndex 处的码元。
-
如 potentialSuffixCodeUnit 不等于 inputCodeUnit,则返回 false。
-
令 i 为 i + 1。
-
若上下文明确为码元(如字符串字面量只含 U+0020 SPACE 到 U+007E (~)),则 "input 以……为后缀(ends with) potentialSuffix" 可用作 "potentialSuffix 为 input 的码元后缀" 的同义说法。
若取值未知,建议显式表达:targetString 是 domain 的码元后缀。若为字面量,则可直接说:domain 以 ".
" 结尾。
字符串 a 码元小于(code unit less than) 字符串 b,当且仅当如下步骤返回 true:
-
如 b 是 a 的码元前缀,则返回 false。
-
如 a 是 b 的码元前缀,则返回 true。
-
令 n 为 a 与 b 第一个码元不同的最小索引。(必有此索引,因为两者互不为前缀。)
-
如 a 的第 n 个码元小于 b 的第 n 个码元,则返回 true。
-
返回 false。
此与 JavaScript 的 <
运算符和其 sort()
方法在字符串数组上的排序顺序一致。该顺序直接比较每个字符串的 16 位码元,因而高效且确定,但对由代理对组成的码点不符合任何实际字母表或字典顺序。[ECMA-262]
例如,U+FF5E FULLWIDTH TILDE (~) 的码点显然小于 U+1F600 (😀),但波浪号为单一码元 0xFF5E,笑脸为两个码元 0xD83D 和 0XDE00,因此笑脸 码元小于 波浪号。
码元子串(code unit substring),即 字符串 string 中从 start 起长度为 length 的子串,按如下步骤确定:
-
断言:start 和 length 为非负数。
-
令 result 为空字符串。
-
对于 i 属于 区间 [start, start + length),将 string 的第 i 个码元追加到 result。
-
返回 result。
按位置取码元子串(code unit substring by positions),即 字符串 string 从 start 到 end,等价于 start 起长度 end − start 的码元子串。
至字符串结尾的码元子串,即 字符串 string 从 start 到结尾,等价于从 start 到 string 的长度的码元子串。
在 "Hello world
" 中,从 1 起长度为 3 的码元子串为
"ell
",也可表示为从 1 到 4 的码元子串。
这些算法中的数字应理解为码元之间的位置,而不是码元的索引。返回的子串是这两个位置间的码元。例如,空字符串内从 0 到 0 的码元子串仍为空字符串,即使该字符串并无索引 0 的码元。
码点子串(code point substring),即在字符串 string 中从 start 起长度为 length 的码点子串,按如下步骤确定:
-
断言:start 和 length 为非负数。
-
令 result 为空字符串。
-
对于 i 属于 区间 [start, start + length),将 string 的第 i 个码点追加到 result。
-
返回 result。
按位置取码点子串(code point substring by positions),即 字符串 string 从 start 到 end,等价于 start 起长度 end − start 的码点子串。
至字符串结尾的码点子串,即 字符串 string 从 start 到结尾,等价于从 start 到 string 的码点长度的码点子串。
一般情况下,开发者传入位置或长度时用码元子串,与 JavaScript 字符串索引方式一致。如 CharacterData
类的方法。[DOM]
其他场合更适合用码点子串。例如 "👽
" 中从 0 起长度 1 的码点子串为 "👽
",而同样位置的码元子串为只含单个代理项 U+D83B 的字符串。
要ASCII 小写化字符串,将该字符串中的所有ASCII 大写字母替换为对应的码点的ASCII 小写字母。
要ASCII 大写化字符串,将该字符串中的所有ASCII 小写字母替换为对应的码点的ASCII 大写字母。
如字符串 A 的ASCII 小写化与 B 的ASCII 小写化一致,则 A 与 B ASCII 不区分大小写(ASCII case-insensitive)匹配。
要ASCII 解码(ASCII decode)字节序列 input,执行以下步骤:
要去除换行符(strip newlines) 字符串中的 U+000A LF 和 U+000D CR 码点。
要规范化换行符(normalize newlines),将字符串中的每对 U+000D CR U+000A LF 码点替换为单个 U+000A LF 码点,再将剩余的 U+000D CR 码点替换为 U+000A LF 码点。
要去除首尾 ASCII 空白符,从字符串开头和结尾删除所有ASCII 空白符。
要去除并合并 ASCII 空白符,将字符串中任意连续多个ASCII 空白符替换为单个 U+0020 SPACE 码点,再去除首尾ASCII 空白符。
要收集一串满足条件的码点,从字符串 input 给定追踪算法位置的位置变量 position,重复如下:
-
令 result 为空字符串。
-
当 position 未越过 input 结尾,且 position 处 码点满足 condition 时:
-
将该码点追加到 result。
-
position 加 1。
-
-
返回 result。
要跳过 ASCII 空白符,在字符串 input 中,给定位置变量 position,收集所有ASCII 空白符(结果丢弃,仅更新 position)。
要严格分割字符串(strictly split a string) input,以 码点 delimiter 为分隔符:
-
令 position 为 input 的位置变量,初始指向开头。
-
令 token 为收集所有不等于 delimiter 的码点所得的字符串,给定 position。
-
追加 token 到 tokens。
-
当 position 未越过 input 结尾:
-
返回 tokens。
此算法为“严格分割”,而后文的按 ASCII 空白符分割和按逗号分割更宽松(对分隔符间空白符有特殊处理)。
要按 ASCII 空白符分割字符串(split a string on ASCII whitespace) input:
-
令 position 为 input 的位置变量,初始指向开头。
-
令 tokens 为字符串的列表,初始为空。
-
当 position 未越过 input 结尾:
-
追加 token 到 tokens。
-
返回 tokens。
要按逗号分割字符串(split a string on commas) input:
-
令 position 为 input 的位置变量,初始指向开头。
-
令 tokens 为字符串的列表,初始为空。
-
当 position 未越过 input 结尾:
-
令 token 为收集所有非 U+002C (,) 码点所得字符串。
token 可能为空字符串。
- 去除首尾 ASCII 空白符。
-
追加 token 到 tokens。
-
如 position 未越过 input 结尾:
-
-
返回 tokens。
要串联(concatenate)字符串列表 list,可选分隔符 separator,执行如下步骤:
要序列化集合 set,返回其用 U+0020 SPACE 串联的结果。
4.8. 时间(Time)
表示时间时,使用 时刻(moment)和时间段(duration)规范类型。创建和与 JavaScript 交换这些类型时,请遵循 High Resolution Time § 3 规范作者工具中的建议。[HR-TIME]
5. 数据结构(Data structures)
传统上,规范常用一些语义共识的、但具体边界模糊的规范层数据结构。这通常效果良好,但在边界情况(如迭代顺序、或向有序集合追加已包含的项时)会引发歧义。对更复杂数据结构(如映射)的记法和表达也因此不统一。
本标准定义了一小组通用数据结构及其操作与表述方式,以便规范间统一表述。
5.1. 列表(Lists)
列表(list)是一种规范类型,包含一组有限且有序的项(item)。
为方便记法,可用 « » 括起,逗号分隔各项,表示列表。索引用方括号,基于0。除用于exists外,索引不可越界。
令
example 为列表 « "a
",
"b
", "c
", "a
" »。则 example[1] 是字符串 "b
"。
为记法方便,可用多重赋值,将多个变量赋为列表各项,形式为 « 变量1, 变量2, … »。变量数须等于列表的长度,各变量赋值为对应项。
当列表内容不完全可控(如源自用户输入),多重赋值前应检查长度。
-
若 list 的长度不为
3
,则返回失败。 -
令 « a, b, c » 为 list。
向非有序集合的列表 A 扩展(extend) B,等价于对 B 的每个项,追加到 A。
向非有序集合的列表前置(prepend)即将给定项加到开头。
对非有序集合的列表,替换(replace)所有满足条件的项为指定项,否则不操作。
如为有序集合,上述定义有调整,详见下文有序集合追加、前置和替换。
对列表或集合,插入(insert)项于指定索引前,若索引为0则为前置。
对列表或集合,移除(remove)所有满足条件的项,否则不操作。
移除 x 自列表 « x, y, z, x » 后,所得列表为 « y, z »。
移除所有以 "a
" 开头的字符串,如列表 «
"a
", "b
", "ab
", "ba
" »,结果为 « "b
",
"ba
" »。
列表若包含某项,则称其包含(contain)该项。也可说 list[index] 存在(exists)。
对列表,取索引(get the indices)即返回区间 [0, 长度)。
对列表或集合,迭代(iterate/for each)每项,写作 "对于 item 属于 list",随后对 item 操作。
对列表,克隆(clone) list,即新建同类型列表 clone,对 list 每项 追加到 clone,顺序一致。
这为“浅克隆”,各项本身不复制。
令
original 为有序集合 «
"a
", "b
", "c
" »。克隆 original 得新有序集合 clone,如在 clone 中用替换将 "a
" 换为 "foo
",结果为 «
"foo
", "b
", "c
" »,而 original[0] 仍为 "a
"。
对列表,升序排序(sort in ascending order),给定小于算法 lessThanAlgo,新建列表 sorted,内容按 lessThanAlgo 升序。相等项保持原顺序。
对列表,降序排序(sort in descending order)同理,但为降序。
令
original 为列表 « (200,
"OK
"), (404, "Not Found
"), (null, "OK
") »。若比较规则为第二项项码元小于,升序结果为 « (404,
"Not Found
"), (200, "OK
"), (null, "OK
") »。
列表类型源自 JavaScript 规范(大写为 List);此处简化描述并扩展了操作词汇。JavaScript 需 List 时可用此定义。[ECMA-262]
5.1.1. 栈(Stacks)
部分列表被指定为栈(stack)。栈本质上是列表,但操作时通常使用下列专用操作,而不用追加、前置或移除。
从栈弹栈(pop):若栈非空,则移除其最后一项并返回,否则返回空。
对栈窥栈(peek):若栈非空,则返回其最后一项,否则返回空。
虽然栈是列表,但不应对其使用for each,而应结合while与pop。
5.1.2. 队列(Queues)
部分列表被指定为队列(queue)。队列本质上是列表,但操作时通常使用下列专用操作,而不用追加、前置或移除。
从队列出队(dequeue):若队列非空,则移除其第一项并返回,否则返回空。
虽然队列是列表,但不应对其使用for each,而应结合while与dequeue。
5.1.3. 集合(Sets)
部分列表被指定为有序集合(ordered set)。有序集合是列表,但其语义要求不含重复项。
Web 平台几乎总要求有序集合,以保证不同浏览器间枚举顺序一致。即使无须关注顺序,也仍用有序集合,实现可因顺序不可见而优化。
向有序集合追加(append):若集合已包含该项,则无操作,否则执行列表的追加。
向有序集合 A扩展(extend) B,即遍历 B 各项 item,追加到 A。
向有序集合前置(prepend):若集合已包含该项,则无操作,否则执行列表的前置。
在有序集合 set 内替换(replace) item 为 replacement:如 set 包含 item 或 replacement,则用 replacement 替换首个出现,并移除其它所有实例。
在有序集合 « "a", "b", "c" » 中替换 "a" 为 "c",结果为 « "c", "b" »;在 « "c", "b", "a" » 中结果也为 « "c", "b" »。
有序集合 set 是另一有序集合 superset 的子集(subset)(superset 相应是超集(superset)),当且仅当 set 的每项 item 均被 superset 包含。
集合 A 等于(equal)集合 B,当且仅当 A 是 B 的子集且 A 是 B 的超集。
集合 A 与 B 的交集(intersection),为新建集合 set,遍历 A 各项 item,如 B 包含 item,则追加至 set。
集合 A 与 B 的并集(union),为克隆 A 得 set,再遍历 B 各项 item,追加至 set。
集合 A 与 B 的差集(difference),为新建集合 set,遍历 A 各项 item,如 B 不包含 item,追加至 set。
区间(the range) n 到 m(含端点),新建有序集合,含 n 至 m 的所有整数,递增排序,前提是 m ≥ n。
区间(the exclusive range) n 到 m(不含 m),新建有序集合,含 n 至 m-1 的所有整数,递增排序,前提是 m > n。若 m = n,则为新建空集合。
对于 区间 1 到 4,遍历 n …
5.2. 映射(Maps)
有序映射(ordered map),有时简称“映射(map)”,是一种规范类型,包含有限个有序的元组,每个元组由键(key)和值(value)组成,且每个键唯一。每个这样的元组称为条目(entry)。
与有序集合一样,默认假定映射有序以确保实现之间的互操作性。
可用字面量记法表示有序映射,用 «[ ]» 括起,条目以 key → value 记法表示,条目间用逗号分隔。
令
example 为有序映射 «[
"a
" → `x
`, "b
" → `y
` ]»,则
example["a
"] 为字节序列 `x
`。
要在有序映射 map 中,给定 键 key 及可选 default,获取条目值(get the value of an entry):
也可用下标符号直接写 map[key] 获取值。若需默认值,可加短语 with default 及默认值。
如
map["test
"]存在,则返回 map["test
"]。
令 example 为有序映射 «[ "a
" → "x
",
"b
" → "y
" ]»,则 example["a
"] 等价于
example["a
"] with default "z
",即
"x
"。example["c
"] 会触发断言;example["c
"] with default
"z
" 为 "z
"。
要在有序映射中设定条目值(set the value of an entry),若已含指定键则更新,否则加到末尾。可直接写“设 map[key] 为 value”。
要在有序映射移除条目(remove an entry),即移除所有满足条件的条目,无则不操作。若条件为某键,亦可写“移除 map[key]”。
要清空(clear)有序映射,即移除其所有条目。
有序映射若含有指定键条目,即存在该键的条目。亦可写 map[key] exists。
要获取有序映射的键(get the keys),返回其所有键组成的新有序集合。
要获取有序映射的值(get the values),返回其所有值组成的新列表。
要遍历(iterate/for each)有序映射 map,可写“对于 key → value 属于 map”,随后对 key 和 value 操作。
要克隆(clone)有序映射 map,新建有序映射 clone,对每对 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,会同时影响 original。
要升序排序(sort in ascending order)映射 map,给定小于算法 lessThanAlgo,新建映射 sorted,按 lessThanAlgo 比较条目升序。相等者保持原顺序。
要降序排序(sort in descending order)映射 map,同理为降序。
5.3. 结构体(Structs)
结构体(struct)是一种规范类型,包含有限组项(item),每项有唯一且不可变的名称(name)。每个项持有特定类型的值。
一个 email 是结构体(struct)示例,包含 local part(字符串)和 host(主机)。
伪算法示例:
- 令 email 为 local part 为 "
hostmaster
" 且 host 为infra.example
的 email。 - …
5.3.1. 元组(Tuples)
元组(tuple)是结构体,其项有顺序。为方便记法,可用括号括起、逗号分隔各项的字面量写法。需上下文能推断各项名称,可在首次出现前说明元组名称。可用下标(0 起)访问某项,索引不得越界。
status 是元组(tuple)示例,包含 code(数字)和 text(字节序列)。
伪算法示例:
- 令 statusInstance 为 status (200, `
OK
`)。 - 将 statusInstance 设为 (301, `
FOO BAR
`)。 - 如果 statusInstance 的 code 为 404,则……
最后一步也可写作“如果 statusInstance[0] 为 404,则……”。若元组项无显式定义,可能更适合此写法。
并非所有结构体都是元组是有意为之。Infra 用户可能需在不破坏依赖的字面量写法下增加新项名,此时不宜用元组。
6. JSON
本节算法采用 JavaScript 规范的约定。[ECMA-262]
要将 JSON 字符串解析为 JavaScript 值,给定字符串 string:
-
返回 ? Call(%JSON.parse%, undefined, « string »)。
要将 JSON 字节解析为 JavaScript 值,给定字节序列 bytes:
-
令 string 为对 bytes 执行 UTF-8 解码 的结果。[ENCODING]
-
返回 将 JSON 字符串解析为 JavaScript 值给定 string 的结果。
要将 JavaScript 值序列化为 JSON 字符串,给定 JavaScript 值 value:
-
令 result 为 ? Call(%JSON.stringify%, undefined, « value »)。
未传递额外参数给 %JSON.stringify%,结果字符串无多余空白。
-
如 result 为 undefined,则抛出
TypeError
。若 value 不可表示为 JSON(如 undefined 或函数),会发生此情况。
-
返回 result。
要将 JavaScript 值序列化为 JSON 字节,给定 JavaScript 值 value:
-
令 string 为 将 JavaScript 值序列化为 JSON 字符串给定 value 的结果。
-
返回对 string 执行 UTF-8 编码 的结果。[ENCODING]
上述操作直接作用于 JavaScript 值;因此对象或数组依赖于特定JavaScript realm。标准中通常更便于在 JSON 与无 realm 依赖的映射、列表、字符串、布尔、数字和 null 间转换。
要将 JSON 字符串解析为 Infra 值,给定字符串 string:
-
令 jsValue 为 ? Call(%JSON.parse%, undefined, « string »)。
-
返回 将 JSON 派生 JavaScript 值转换为 Infra 值给定 jsValue 的结果。
要将 JSON 字节解析为 Infra 值,给定字节序列 bytes:
-
令 string 为对 bytes 执行 UTF-8 解码 的结果。[ENCODING]
-
返回 将 JSON 字符串解析为 Infra 值给定 string 的结果。
要将 JSON 派生 JavaScript 值转换为 Infra 值,给定 JavaScript 值 jsValue:
要将 Infra 值序列化为 JSON 字符串,给定字符串、布尔、数字、null、列表或以字符串为键的映射 value:
-
令 jsValue 为 将 Infra 值转换为 JSON 兼容的 JavaScript 值给定 value 的结果。
-
返回 ! Call(%JSON.stringify%, undefined, « jsValue »)。
未传递额外参数给 %JSON.stringify%,结果无多余空白。
要将 Infra 值序列化为 JSON 字节,给定字符串、布尔、数字、null、列表或以字符串为键的映射 value:
-
令 string 为 将 Infra 值序列化为 JSON 字符串给定 value 的结果。
-
返回对 string 执行 UTF-8 编码 的结果。[ENCODING]
要将 Infra 值转换为 JSON 兼容的 JavaScript 值,给定 value:
-
如 value 为列表:
-
令 jsValue 为 ! ArrayCreate(0)。
-
令 i 为 0。
-
遍历 value 的每个 listItem:
-
令 listItemJSValue 为 将 Infra 值转换为 JSON 兼容的 JavaScript 值 给定 listItem 的结果。
-
执行 ! CreateDataPropertyOrThrow(jsValue, ! ToString(i), listItemJSValue)。
-
令 i = i + 1。
-
-
返回 jsValue。
-
-
断言:value 为映射。
-
令 jsValue 为 ! OrdinaryObjectCreate(null)。
-
遍历 value 的每组 mapKey → mapValue:
-
断言:mapKey 为字符串。
-
令 mapValueJSValue 为 将 Infra 值转换为 JSON 兼容的 JavaScript 值 给定 mapValue 的结果。
-
执行 ! CreateDataPropertyOrThrow(jsValue, mapKey, mapValueJSValue)。
-
-
返回 jsValue。
规范中通常不宜直接操作 JavaScript 值,应优先用将 Infra 值序列化为 JSON 字符串或将 Infra 值序列化为 JSON 字节。如有特殊需求,请提交 issue讨论。
7. 宽容 base64(Forgiving base64)
要宽容 base64 编码(forgiving-base64 encode)给定字节序列 data,对 data 应用 RFC 4648 第 4 节定义的 base64 算法并返回结果。[RFC4648]
该操作之所以命名为 宽容 base64 编码,是为与宽容 base64 解码对称;后者与 RFC 有区别,定义了某些输入的错误处理。
要宽容 base64 解码(forgiving-base64 decode)给定字符串 data,执行如下步骤:
-
移除 data 中所有ASCII 空白符。
-
若 data 的码点长度能被 4 整除:
-
如 data 以一个或两个 U+003D (=) 码点结尾,则将其移除。
-
-
若 data 的码点长度除以 4 余 1,则返回失败。
-
若 data 包含不属于下列之一的码点:
- U+002B (+)
- U+002F (/)
- ASCII 字母数字
则返回失败。
-
令 output 为空字节序列。
-
令 buffer 为可追加比特的空缓冲区。
-
令 position 为 data 的位置变量,初始指向开头。
-
当 position 未越过 data 结尾时,重复:
-
如 buffer 非空,其长度为 12 或 18 比特。若为 12 比特,丢弃后 4 位,余下 8 位按大端解释为一个 8 位整数;若为 18 比特,丢弃后 2 位,余下 16 位按大端解释为两个 8 位整数。将所得 1 或 2 字节依次追加到 output。
丢弃比特意味着如 "
YQ
" 和 "YR
" 都还原为 `a
`。 -
返回 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, 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。