String.prototype.length 回傳的是 UTF-16 程式碼單元數,不是 使用者感知的字元數(grapheme clusters)。
《JavaScript 權威指南》第 39 頁說明:JS 字串是 UTF-16 編碼序列。BMP 字元(U+0000 ~ U+FFFF)佔 1 個程式碼單元;補充平面字元(U+10000 ~ U+10FFFF)佔 2 個程式碼單元(surrogate pair)。
> 'e'.length // 1 (BMP 字元)
1
> '𝑒'.length // 2 (U+1D452 數學斜體小寫 e,補充平面)
2
> '😀'.length // 2 (Emoji U+1F600)
2
> '👨👩👧👦'.length // 11 (家庭 Emoji:4 個基礎字元 + 3 個 ZWJ 連接符)
11
提問者看到 \ud835\udc52(這是 𝑒 的 UTF-16 代理對表示),以為是字母 e,實際上它是數學斜體小寫 e(MATHEMATICAL ITALIC SMALL E),屬於補充平面字元,故 length === 2。
const str = '𝑒😀ab';
[...str].length; // 4
Array.from(str).length; // 4
str.match(/[\s\S]/gu)?.length; // 4
const segmenter = new Intl.Segmenter('zh', { granularity: 'grapheme' });
[...segmenter.segment('👨👩👧👦')].length; // 1 (視為 1 個字素簇)
[...segmenter.segment('𝑒')].length; // 1
| 字串 | .length | [...str].length | Intl.Segmenter |
|---|---|---|---|
| abc | 3 | 3 | 3 |
| 𝑒 (U+1D452) | 2 | 1 | 1 |
| 😀 (U+1F600) | 2 | 1 | 1 |
| 👨👩👧👦 | 11 | 11 | 1 |
| é (e + 組合鋭音符) | 2 | 2 | 1 |
.length、substring、slice(按程式碼單元操作)[...str].length 或 Intl.SegmenterIntl.Segmenter 符合使用者直覺.lengthJS 設計時(1995)Unicode 僅有 BMP,UTF-16 固定 2 位元組/字元。後來 Unicode 擴展至補充平面,JS 為相容性保留 UTF-16 並引入 surrogate pair,導致 length 語義固化為「程式碼單元數」。