codePointAt() 能正确获取 Unicode 辅助平面字符的完整码点,而 charCodeAt() 在代理对处拆分导致错误;遍历时需跳过低代理单元,用 fromCodePoint() 还原字符,.length 返回代码单元数而非字符数。

JavaScript 中的 codePointAt() 是处理 Unicode 高位字符(如大多数 Emoji)的正确方法,因为它能正确返回完整的 Unicode 码点(code point),而不会像 charCodeAt() 那样在代理对(surrogate pair)处拆分出错误的 16 位值。
为什么需要 codePointAt() 而不是 charCodeAt()
Emoji(如 ?、??、?❤️?)常属于 Unicode 辅助平面(U+10000 及以上),在 UTF-16 编码中以两个 16 位代码单元(即“代理对”)表示。JavaScript 字符串底层用 UTF-16 存储,所以 charCodeAt() 每次只读取一个代码单元,对高位字符会返回不完整的值(如高代理或低代理值),无法还原原始 Emoji。
-
'?'.charCodeAt(0)→55357(高代理,U+D83C) -
'?'.charCodeAt(1)→56965(低代理,U+DF33) -
'?'.codePointAt(0)→127795(即 U+1F333,完整码点)
如何安全遍历含 Emoji 的字符串
直接用 for (let i = 0; i 配合 <code>codePointAt(i) 是可行的,但要注意:当遇到代理对时,第二个代码单元应被跳过,避免重复处理。
- 推荐使用
for...of循环——它按 Unicode 字符(而非 UTF-16 代码单元)迭代,天然支持 Emoji: - 若需索引控制(如查找位置),可用
Array.from(str)将字符串转为真正按字符切分的数组: - 手动跳过代理对(不推荐,仅作理解):
if (str.codePointAt(i) > 0xFFFF) i++;
for (const ch of 'Hello ??❤️') { console.log(ch, ch.codePointAt(0).toString(16)); }
const chars = Array.from('??❤️'); // ['??', '❤️'],长度为 2
还原 Emoji:fromCodePoint() 与 toString(16)
codePointAt() 返回的是十进制整数,可配合 String.fromCodePoint() 还原字符,也可用 .toString(16) 转为十六进制便于查表。
-
String.fromCodePoint(128104)→'?' -
String.fromCodePoint(0x1F468, 0x200D, 0x1F4BB)→'??'(组合型 Emoji,支持多码点) -
0x1F333.toString(16)→'1f333',对应 ?(注意补零成 4–6 位更规范)
常见误区提醒
不要混淆“字符长度”和“字符串 .length”。.length 返回 UTF-16 代码单元个数,对 Emoji 可能偏大;判断是否为高位字符,应检查 codePointAt(i) >= 0x10000,而不是 ch.length > 1(单个 Emoji 字符的 .length 始终是 1)。
-
'?❤️?'.length→7(含多个代理对和 ZWJ 连接符),但Array.from('?❤️?').length→1 -
'a'.codePointAt(0) === 'a'.charCodeAt(0)→true(BMP 内字符两者等价) -
str.codePointAt(-1)返回undefined,负索引不生效,别误以为类似 Python

















