codePointAt() 是 JavaScript 中唯一能正确获取 Unicode 码点(含 BMP 外字符)的方法,它自动处理代理对,避免 charCodeAt() 返回错误代码单元的问题。

String.prototype.codePointAt() 是 JavaScript 中唯一能正确获取 Unicode 码点(包括超出基本多文种平面 BMP,即 U+0000–U+FFFF 的字符)的方法。它解决了 charCodeAt() 在处理代理对(surrogate pairs)时返回错误码元(code unit)的问题。
为什么需要 codePointAt()?
JavaScript 字符串内部使用 UTF-16 编码。对于码点 ≥ U+10000 的字符(如大部分 emoji、古文字、部分数学符号),UTF-16 用两个 16 位代码单元(即代理对:一个高位代理 + 一个低位代理)表示。而 charCodeAt() 每次只读取一个代码单元,导致:
- 对 emoji 如
"?"(U+1F60A),charCodeAt(0)返回55357(0xD83D),不是真实码点; - 需手动判断并组合代理对,逻辑复杂且易错;
-
codePointAt()自动识别代理对,直接返回完整码点(如"?".codePointAt(0) === 0x1F60A // true)。
如何正确使用 codePointAt()
调用 codePointAt(index) 时,传入字符串中“字符位置”的索引(注意:是 UTF-16 代码单元索引,不是“视觉字符数”)。关键要点:
- 若该位置是高位代理(0xD800–0xDFFF 范围内),且后一位是有效的低位代理,则返回组合后的码点;
- 否则,返回该位置的代码单元值(与
charCodeAt()相同); - 返回值为数字,可直接用于
String.fromCodePoint()还原; - 推荐始终检查返回值是否
undefined(越界时)或使用Number.isNaN()辅助判断(但通常只需判undefined)。
遍历字符串中的所有码点(而非代码单元)
要逐个访问每个 Unicode 字符(含 emoji、CJK 扩展字等),不能用传统 for (let i = 0; i ,因为 str.length 统计的是代码单元数,不是字符数。正确方式:
- 用
for...of循环(ES2015+)——它按 Unicode 字符迭代,底层已处理代理对; - 手动跳过代理对:检测
codePointAt(i)后,根据返回码点长度决定下标偏移(BMP 字符占 1 单元,辅助平面字符占 2 单元); - 示例安全遍历:
function forEachCodePoint(str, callback) {
for (let i = 0; i 0xFFFF ? 2 : 1;
}
}常见误区与注意事项
使用 codePointAt() 时容易忽略的细节:
- 它不改变字符串本身,只是读取;
- 索引仍以 UTF-16 代码单元为单位(例如
"??".length === 4,但它是 1 个用户感知字符,含 2 个代理对 + ZWJ),codePointAt(0)返回第一个代理对组合的码点(U+1F469),后续需跳 2 位; - 复合 emoji(如带肤色修饰符或 ZWJ 序列)仍是多个独立码点,
codePointAt()不做语义合并; - 若需真正按“用户看到的字形”切分(如支持变体选择符、组合标记),需用 Intl.Segmenter 或第三方库(如 grapheme-splitter),
codePointAt()仅解决 BMP 外码点提取问题。










