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"]