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/proposal-reversible-string-split.md.
  • 简体中文
  • Reversible string split S1

    中文标题:可逆字符串分割

    提案概览
    提案速览

    该提案针对 JavaScript 的 split 方法,该方法截断结果而不返回余项,这与大多数语言分割 N-1 次并返回余项的行为不同。它提议添加 String.prototype.splitN 以实现可逆分割,这对于 INI 等格式的前缀分割很有用。

    Note

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

    可逆字符串分割

    图片来自 @DasSurma

    状态

    提案发起人:Luca Casonato

    作者:Luca Casonato

    阶段:1

    动机

    JavaScript 中的字符串分割方法的行为与几乎所有其他语言中的字符串分割方法不同。在 JavaScript 中,split(sep, N) 本质上是一个常规的分割,但输出数组被截断为前 N 个值。JavaScript 认为 N 既表示分割次数也表示返回项数。

    在大多数其他语言中,splitN 而是将原始字符串分割成 N 项,包括一个余项。因此,它们实际分割了 N-1 次。返回数组中的最后一项包含字符串的“余项”。

    # Perl
    
    print join('\n', split(/\|/, 'a|b|c|d|e|f', 2))
    
    # a
    # b|c|d|e|f
    <!-- PHP -->
    <?php
    
    print join("\n", explode("|", "a|b|c|d|e|f", 2));
    
    # a
    # b|c|d|e|f
    # Ruby
    
    print 'a|b|c|d|e|f'.split('|', 2)
    
    # ["a", "b|c|d|e|f"]
    // Go
    
    package main
    
    import (
      "fmt"
      "strings"
    )
    
    func main() {
      fmt.Printf("%#v", strings.SplitN("a|b|c|d|e|f", "|", 2))
    }
    
    // []string{"a", "b|c|d|e|f"}
    // Rust
    
    fn main() {
      let v = "a|b|c|d|e|f".splitn(2, "|").collect::<Vec<_>>();
      println!("{:?}", v);
    }
    
    // ["a", "b|c|d|e|f"]
    // Java
    
    class Playground {
      public static void main(String[] args) {
        String s = new String("a|b|c|d|e|f");
        for(String val : s.split("\\|", 2)) {
          System.out.println(val);
        }
      }
    }
    
    // a
    // b|c|d|e|f
    # Python
    
    print('a|b|c|d|e|f'.split('|', 2))
    
    # ['a', 'b', 'c|d|e|f']
    // JavaScript
    
    console.log("a|b|c|d|e|f".split("|", 2));
    
    // ["a", "b"]

    这里的 8 种语言中有 6 种达成了共识。它们认为 N 表示“返回的项数”,余项作为返回数组的最后一项。这意味着它们实际上分割了 N-1 次。

    Python 也同意余项应作为数组的最后一项返回。不过,它对于 N 的含义与其余语言不同。Python 分割 N 次,并返回 N+1 项。

    然而,JavaScript 完全背离了这一共识:它分割 N 次,返回 N 项,且根本不返回余项。它是唯一这样做的语言。

    尽管 Python 和其他语言之间略有不同,但它们的所有算法都有一个 JavaScript 所缺少的共同特征:它们的分割是可逆的。这意味着如果您将一个字符串分割成 N 项,您可以将其重新连接起来而不会丢失任何信息。

    可逆分割具有这样的性质:对于任何字符串 V、任何分隔符 S 以及任何无符号非零整数 N,以下表达式成立:

    join(S, V.split(S, N)) == V;

    这种可逆性使得字符串分割可以用于一些非常有用的任务,而当前的分割方法在这些任务中无法工作。最常见的用例是前缀分割:

    前缀分割

    许多格式都是字符分隔的。能够轻松地在那些预定义的“分割点”处将一个字符串分割成两部分是非常有用的。例如,INI 文件格式使用 = 字符来分隔键值对,并使用 \n 字符来分隔各个键值对。

    key = value
    other_key = 'value contains an = sign'

    使用 JavaScript 当前的“split”,解析这种格式不如使用“更流行”的分割算法那样直观:

    // 当前的 JavaScript
    const ini = Deno.readTextFileSync("./test.ini");
    const entries = ini.split("\n").map((line) => {
      const [key, ...rest] = line.split("=");
      return [key, rest.join("=")];
    });
    
    // 其他语言
    const ini = Deno.readTextFileSync("./test.ini");
    const entries = ini.split("\n").map((line) => line.splitN("=", 2));

    注意: 我意识到可以使用不同的“解析器”来提高效率。但这并不是重点。重点是让显而易见的事情变得容易。

    这种行为不仅与 INI 文件格式相关,也适用于 HTTP/1.1 中的 HTTP 头、Cookie 头中的键值对等等。

    提案

    该提案旨在为 JavaScript 添加可逆字符串分割支持。该提案建议新增一个 String.prototype.splitN 方法,该方法最多将输入字符串分割 N-1 次,返回 N 个子字符串。最后一项包含字符串的余项。

    console.log("a|b|c|d|e|f".splitN("|", 2));
    // ["a", "b|c|d|e|f"]

    命名来源于 Rust 和 Go。

    问答

    这可以作为 split 方法的额外选项吗?

    可以。这也可以是 split 新选项袋中的一个选项。例如:

    console.log("a|b|c|d|e|f".split("|", { n: 2 }));
    // 或者
    console.log("a|b|c|d|e|f".split("|", 2, true));
    // 或者
    console.log("a|b|c|d|e|f".split("|", { n: 2, remainder: true }));
    // 或者
    console.log("a|b|c|d|e|f".split("|", 2, { remainder: true }));

    不过,第一个选项可能会使用户感到困惑,因为 split("|", 2)split("|", { n: 2 }) 的返回值不同并不明显。这种重载在 Web 平台上存在(例如 addEventListener),但您使用的形式并不影响行为。

    第二个选项更清晰,但同时也更不清晰,因为第三个参数中的 true 值代表什么并不明显。

    第三个选项相当清晰,但也是最冗长的。这种冗长可能会使其使用起来很繁琐。

    第四个选项可能是“最干净”的。因为在当前的引擎中,额外的选项会被忽略,这可能使其看起来像是支持了额外的选项,但实际上并不支持——它只是被忽略了。

    最终应该使用这 4 个选项中的哪一个应由委员会整体决定。我个人并不太在意(尽管我更喜欢 splitN 选项)。

    我喜欢 split 当前的行为!

    别担心!它不会被移除。新的 splitN 函数旨在简化上述用例。您可以继续使用现有的 split

    为什么 JavaScript 的 split 与其他语言如此不同?

    说实话:我们并不真正知道。我们询问过的人还没有人能够给出一个好的答案。我们确实知道的是:

    • 1997 年 6 月发布的 Netscape Navigator 4 是第一个以其当前行为支持 split 方法第二个参数的浏览器。10 个月前发布的 NN3 没有此功能。
    • 4 个月后发布的 IE 4 没有此功能/行为。
    • 第一个指定此行为的 ECMA262 版本是 ES3。
    • 这种行为并非来自 Java。Java 仅在 1.4 版本(2002 年)中才添加了 String.split 方法。

    感谢 @hax 和 @aimingoo 对此所做的研究。