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/1/proposal-compare-strings-by-codepoint.md.
  • 简体中文
  • Compare Strings by Codepoint S1

    中文标题:按码点比较字符串

    提案概览
    提案速览

    该提案引入了一个静态方法String.codePointCompare(a, b),用于按Unicode码点比较字符串,解决了JavaScript基于代码单元的比较与基于UTF-8的系统之间的不一致。prototype.sort的比较器。已考虑手动迭代或空区域设置排序等替代方案,但认为不够优化。

    Note

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

    按码点比较字符串

    一个TC39提案,旨在新增一个按Unicode码点比较字符串的方法。

    状态

    TC39流程

    阶段:1

    ** champions **:

    • Mathieu Hofman (@mhofman)
    • Mark S. Miller (@erights)
    • Christopher Hiller (@boneskull)

    动机

    JavaScript中字符串的背景

    JavaScript将字符串表示为16位/UCS-2字符的序列。对于格式良好的字符串,这些字符是UTF-16代码单元。UTF-16使用代理对来表示基本多语言平面之外的任何Unicode码点。

    这意味着,码点在[0x010000 - 0x10FFFF]范围内的Unicode字符将被表示为2个代码单元,第一个是[0xD800 - 0xDBFF]范围内的前导代理,第二个是[0xDC00 - 0xDFFF]范围内的尾随代理。

    如需进一步阅读,请参阅@mathiasbynens关于JavaScript内部字符编码的帖子。

    字符串编码对JavaScript程序的影响

    这种编码选择在以下主要情况下被JavaScript程序观察到:

    • 对字符串的索引访问。这扩展到所有涉及偏移量或长度的String API。
    • 使用没有uv标志的RegExp匹配字符串。
    • 比较字符串。

    与索引访问不同,字符串上的迭代器确实按码点操作。类似地,uv正则表达式标志允许匹配整个Unicode码点而不是代码单元/代理半部分。然而,不存在允许按码点比较字符串的String API。

    因为JavaScript按16位代码单元比较字符串,所以[0xE000 - 0xFFFF]范围内的任何码点都将排在用于编码[0x010000 - 0x10FFFF]范围内码点前半部分的前导代理之后。

    与其他系统的互操作性

    这种比较行为使JavaScript与最终按Unicode码点比较字符串的其他语言或系统不一致。这包括任何使用UTF-8作为字符串编码并依赖字节比较的语言或系统(UTF-8确实保持排序顺序)。

    使用UTF-8编码的语言示例包括Swift和Golang。SQLite默认也使用UTF-8编码字符串。因此,这些系统中字符串的排序顺序可能与JavaScript中相同字符串的排序顺序不匹配。

    与区域设置无关的比较

    数据不仅仅呈现给人类用户。某些数据处理涉及排序,并且需要确定性和可移植性。按Unicode码点比较满足这些要求,而基于区域设置的比较则不满足。

    提案

    一个String.codePointCompare(a, b)(实际名称待定),可用于按码点比较两个字符串。该函数可用作Array.prototype.sort的参数。

    示例

    const arr = [
      '\u{ff42}', // 全角拉丁小写字母B
      '\u{1d5ba}', // 数学无衬线小写A
      '\u{63}', // 拉丁小写字母C
    ];
    
    console.log('native compare', [...arr].sort()); // [ 'c', '𝖺', 'b' ]
    console.log('locale compare', [...arr].sort((a, b) => a.localeCompare(b))); // [ '𝖺', 'b', 'c' ]
    console.log('null locale compare', [...arr].sort(new Intl.Collator('zxx').compare)); // [ '𝖺', 'b', 'c' ]
    console.log('codepoint compare', [...arr].sort(String.codePointCompare)); // [ 'c', 'b', '𝖺' ]

    考虑的替代方案

    手动迭代

    可以使用手动迭代字符串来编写比较器,这是目前的状态。这可以通过手动同步推进字符串迭代器,或使用索引访问并获取代码单元来实现。

    codePointCompare shim
    function codePointCompare(left, right) {
      const leftIter = left[Symbol.iterator]();
      const rightIter = right[Symbol.iterator]();
      for (;;) {
        const { value: leftChar } = leftIter.next();
        const { value: rightChar } = rightIter.next();
        if (leftChar === undefined && rightChar === undefined) {
          return 0;
        } else if (leftChar === undefined) {
          // left is a prefix of right.
          return -1;
        } else if (rightChar === undefined) {
          // right is a prefix of left.
          return 1;
        }
        const leftCodepoint = leftChar.codePointAt(0);
        const rightCodepoint = rightChar.codePointAt(0);
        if (leftCodepoint < rightCodepoint) return -1;
        if (leftCodepoint > rightCodepoint) return 1;
      }
    };

    空区域设置排序

    语言中已有替代比较器,例如String.prototype.localeCompareIntl.Collator.prototype.compare。虽然这些操作基于码点,但它们考虑区域设置并折叠同一等价类中的字符。Stable Formatting提案中的zxx“区域设置”也是如此。

    虽然我们可以想象一个空区域设置的排序选项,不执行任何等价类或字素逻辑,只按Unicode码点比较字符串,但这似乎与Intl的核心背道而驰

    此外,Intl是规范的可选标准化部分,这要求选择退出的JS引擎实现足够的Intl,仅仅是为了提供与国际化关切无关的比较。

    问答

    如果比较器遇到格式错误的字符串,应该返回什么?

    字符串中未匹配的代理项可以回退到使用其代码单元进行比较。可能还有其他可以考虑的替代行为。

    <>这样的比较运算符呢

    因为改变它们的行为将是一个破坏性变更,它们将继续按代码单元进行字典序比较字符串。然而,程序可以改为比较调用提议的码点比较器函数与两个字符串的结果的符号。

    我们应该更改默认的Array.prototype.sort比较吗

    与运算符一样,这将是一个破坏性变更。任何对可移植性感兴趣的人都可以相当容易地提供比较器函数。

    您能否提供一个需要可移植比较的系统的示例?

    Agoric平台实现了集合,这些集合使用明确定义的键排序顺序,而不是使用插入顺序。这些集合可以是“仅堆”,也可以由SQLite数据库支持。为了提供独立于后备存储的一致迭代顺序,堆实现需要使用与SQLite实现的排序顺序兼容的比较器。

    演示

    另请参阅

    https://github.com/endojs/endo/pull/2008

    https://github.com/Agoric/agoric-sdk/issues/10335

    https://github.com/Agoric/agoric-sdk/pull/10299

    https://es.discourse.group/t/builtin-ord-compare-method-for-primitives/724/25