JS 字符串与 Unicode:length、surrogate pair、正确拆分

为什么 \"😊\".length 是 2;charCodeAt vs codePointAt;split('') vs [...str] vs Intl.Segmenter。

#resource / javascript #resource / ecmascript #type / concept #status / growing

[!info] related notes JavaScript / TypeScript 字符串方法 JavaScript 字符串按字符拆分

JS 字符串与 Unicode:length、surrogate pair、正确拆分

1) length 统计的是什么

JavaScript 字符串底层是 UTF-16 编码单元序列。

所以 length 统计的是“编码单元数量”,不一定等于“用户看到的字符数”。

"A".length; // 1
"😊".length; // 2

原因:像 emoji 这类超出 BMP 的字符,会用两个 UTF-16 code unit(代理对 surrogate pair)表示。

2) charCodeAt vs codePointAt

  • charCodeAt(i):返回 UTF-16 code unit(0..65535)
  • codePointAt(i):返回 Unicode code point(更接近“字符”)
const s = "😊";
s.charCodeAt(0);   // 55357 (high surrogate)
s.charCodeAt(1);   // 56842 (low surrogate)
s.codePointAt(0);  // 128522

3) 怎么“按字符”拆分字符串

最常见误区:

  • split('') 会把代理对拆开

推荐:

  • Array.from(str)[...str](按字符串迭代器的码点产出,能处理大多数 surrogate pair)

更细节见:JavaScript 字符串按字符拆分

4) 还有更复杂的“用户感知字符”

即使 [...str] 也不一定等于“用户看到的一个字符”,因为存在:

  • 组合附加符号
  • 旗帜/家庭 emoji(多个码点组合)

如果要按“grapheme cluster”分割,考虑 Intl.Segmenter

const seg = new Intl.Segmenter("en", { granularity: "grapheme" });
const parts = Array.from(seg.segment("a🚀b"), (x) => x.segment);
// ["a", "🚀", "b"]
创建于 2026/3/16 更新于 2026/5/27