charcodeat() 返回utf-16编码单元值,仅适用于bmp字符;codepointat() 返回完整unicode码点,能正确处理代理对,如emoji。

JavaScript 中的 charCodeAt() 和 codePointAt() 看似功能相似,实则反映两种不同层级的字符编码模型:UTF-16 编码单元(code unit)与 Unicode 码点(code point)。理解它们的差异,本质是理解 JavaScript 字符串底层如何表示人类语言中的“字符”。
charCodeAt() 操作的是 UTF-16 编码单元,不是“字符”
charCodeAt(index) 返回字符串在指定位置的 UTF-16 编码单元值(0–65535),即一个 16 位无符号整数。它不关心语义上的“字符”,只按内存中两个字节一组读取:
- 对于基本多文种平面(BMP)内的字符(如英文字母、常用汉字),每个字符占 1 个编码单元,
charCodeAt()返回的就是其 Unicode 码点值; - 对于超出 BMP 的字符(如 emoji 表情 ?、古汉字、部分数学符号),JavaScript 使用代理对(surrogate pair)表示:占用连续两个编码单元(高位代理 + 低位代理),此时调用
charCodeAt(i)只会返回其中一个代理值,而非完整字符的码点。
例如:'?'.charCodeAt(0) 返回 55357(高位代理),'?'.charCodeAt(1) 返回 56832(低位代理)——单独看都毫无语义,合起来才对应 U+1F30D。
codePointAt() 才真正面向 Unicode 码点
codePointAt(index) 是 ES2015 引入的方法,它能识别代理对并返回完整的 Unicode 码点(可高达 0x10FFFF):
- 若位置处于代理对的高位代理处(且后续是合法低位代理),它自动组合并返回正确码点;
- 若位置处于低位代理处,或高位代理后无匹配低位代理,则只返回该编码单元值(与
charCodeAt()一致); - 它更符合开发者对“第 n 个字符”的直觉,尤其在处理 emoji、罕见汉字、增补符号时更可靠。
例如:'?'.codePointAt(0) 直接返回 127757(即 0x1F30D),无需手动拼接;而 'a'.codePointAt(0) 仍返回 97,与 charCodeAt() 结果一致。
字符串长度与遍历方式暴露根本差异
由于代理对占两个编码单元,length 属性返回的是编码单元个数,不是视觉字符数:
-
'??'.length是4(含两个代理对 + 连接符 ZWJ),但实际是 1 个合成 emoji; for (let i = 0; i 遍历时,会把代理对拆成两次迭代,导致逻辑错误;- 推荐使用
for…of循环或Array.from(str),它们基于码点(而非编码单元)进行迭代,天然支持完整字符。
实际建议:何时用哪个?
日常开发中应有明确倾向:
- 避免使用
charCodeAt()处理可能含非 BMP 字符的场景(如用户昵称、国际化文本、emoji 输入); - 需要获取字符真实 Unicode 值时(如做字符分类、正则过滤、国际化排序),优先用
codePointAt(); - 仅在与旧 API 兼容、或明确处理 UTF-16 底层(如自定义编码转换、二进制协议解析)时,才直接操作
charCodeAt(); - 判断字符串是否含非 BMP 字符?可用
[...str].some(ch => ch.codePointAt(0) > 0xFFFF)。
本质上,charCodeAt() 是历史包袱下的低层接口,codePointAt() 是向 Unicode 抽象模型靠拢的现代修正。选对方法,才能让字符串操作真正“所见即所得”。











