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/stage/4/proposal-is-usv-string.md.
  • 简体中文
  • Well-Formed Unicode Strings S4

    中文标题:良构 Unicode 字符串

    提案概览
    提案速览

    该提案解决了验证 JavaScript 字符串是否为良构 UTF-16 的问题,这是与基于 Unicode 的 API 接口所必需的。它添加了两个方法,String.prototype.isWellFormedString.prototype.toWellFormed,分别用于测试和转换字符串。

    Note

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

    良构 Unicode 字符串

    提案发起人:Guy Bedford, Bradley Farias, Michael Ficarra

    状态:第 4 阶段

    问题陈述

    ECMAScript 字符串值是零个或多个 16 位无符号整数的有限有序序列。然而,ECMAScript 对这些整数值除了必须是 16 位无符号整数之外,没有任何限制或要求。在良构字符串中,序列中的每个整数值表示 UTF-16 编码的 Unicode 文本的一个 16 位代码单元。但是,并非所有 UTF-16 代码单元序列都表示 UTF-16 编码的 Unicode 文本。在良构字符串中,范围在 0xD800..0xDBFF(高位代理)和 0xDC00..0xDFFF(低位代理)内的代码单元必须成对且按顺序出现。具有未配对或顺序错误的代理项的字符串是不良构的。

    在 WebIDL 中,可能不良构的字符串使用 DOMString 类型引用。但对于操作 Unicode 文本的接口,WebIDL 还定义了 USVString 类型,作为所有可能的 Unicode 标量值序列的集合,即除代理码点(U+0000..U+D7FFU+E000..U+10FFFF)之外的所有 Unicode 码点,表示 Unicode 文本。

    WebAssembly 组件模型要求良构字符串,一些编译为 JS 的编程语言、许多数据编码、网络接口、文件系统接口等也需要良构字符串。将 JavaScript 字符串与这些 API 接口对接是一个常见用例,因此面临转换负担。特别是因为从 DOMStringUSVString 的转换是有损的(常见选项是用替换字符替换未配对的代理项或抛出错误),在平台内部以及某些用户态用例中,经常需要进行字符串验证。

    提案

    该提案是在 ECMA-262 中定义一个方法,用于验证给定的 ECMAScript 字符串是否为良构。作为 JavaScript/web API 与操作 Unicode 文本的 API 之间接口的高度常见场景,此测试应尽可能高效,理想情况下其开销应与字符串长度无关。除了提高性能外,此方法还能提高执行此测试的代码的可读性,特别是对于那些没有丰富 Unicode 或正则表达式知识的读者。

    该提案还添加了一个方法,通过将任何单独的或顺序错误的代理项(如果存在)替换为 U+FFFD(替换字符)来确保字符串是良构的。此操作模拟了各种 web 和非 ECMAScript API 中已有的操作,并降低了本来需要编写此方法的消费者以不兼容或不正确的方式编写它的可能性。

    这些方法是 String.prototype.isWellFormedString.prototype.toWellFormed,例如可以这样使用:if (!someString.isWellFormed()) { someString = someString.toWellFormed(); }

    算法

    验证算法实际上是标准的 UTF-16 验证算法,遍历字符串并对 UTF-16 代理项进行配对,任何未配对的代理项都会导致验证失败。

    在 JavaScript 中,可以使用正则表达式测试实现:

    !/\p{Surrogate}/u.test(str);

    或者更明确地使用类似下面的算法:

    function isWellFormed(str) {
      for (let i = 0; i < str.length; ++i) {
        const isSurrogate = (str.charCodeAt(i) & 0xF800) == 0xD800;
        if (!isSurrogate) {
          continue;
        }
        const isLeadingSurrogate = str.charCodeAt(i) < 0xDC00;
        if (!isLeadingSurrogate) {
          return false; // unpaired trailing surrogate
        }
        const isFollowedByTrailingSurrogate = i + 1 < str.length && (str.charCodeAt(i + 1) & 0xFC00) == 0xDC00;
        if (!isFollowedByTrailingSurrogate) {
          return false; // unpaired leading surrogate
        }
        ++i;
      }
      return true;
    }

    不幸的是,这些用户态实现需要遍历整个字符串。

    已有实现

    常见问题解答

    这个问题难道现在不能解决吗,为什么还需要内置 API?

    回答这个问题是可能的,但不可能在线性时间内完成。

    性能优化取决于实现,本规范并不保证,但启用一个比用户态验证更快的内置方法是有价值的。也许可以每个字符串缓存良构状态,并在字符串操作中传播该状态,以避免需要线性扫描,例如在字符串已经是 Unicode 标量值列表的情况下,而在涉及包含内部未配对代理项的字符串的操作中,传播状态可能仍需要(重新)扫描。

    消费者在遇到不良构字符串时,除了转换之外还会做其他操作吗?如果不会,为什么不只提供一个转换方法,且对良构字符串有快速路径?

    消费者可能希望在遇到不良构字符串时抛出错误。此外,消费者可能希望将转换或错误延迟到稍后当字符串实际被解释为 Unicode 文本时。这些用例证明了只测试的方法的合理性。

    Polyfill