javascript字符串内部用utf-16编码,length返回代码单元数而非字符数;超bmp码点(如u+1f600)以代理对形式存储,需用codepointat、扩展运算符等api正确处理码点。

JavaScript内部使用UTF-16编码表示字符串,每个字符以16位(2字节)的代码单元(code unit)存储。但Unicode字符总数远超65536(即216),因此不能简单用“一个字符 = 一个代码单元”来理解——关键在于区分Unicode码点(code point)和UTF-16代码单元(code unit)。
Unicode码点与UTF-16代码单元不是一一对应的
Unicode为每个字符分配一个唯一码点(如U+0041表示‘A’,U+1F600表示?)。码点范围目前可达U+10FFFF(约111万)。而UTF-16用16位单元表示,只能直接表达U+0000–U+FFFF(基本多文种平面,BMP)。超出BMP的码点(U+10000–U+10FFFF)必须用两个16位代码单元表示,称为代理对(surrogate pair):
- 高位代理(high surrogate):范围 U+D800–U+DBFF
- 低位代理(low surrogate):范围 U+DC00–U+DFFF
- 二者组合才能还原出一个辅助平面字符,例如 U+1F600 → 0xD83D 0xDE00
JavaScript字符串长度反映的是代码单元数,不是字符数
这是最易踩坑的地方:`str.length` 返回的是UTF-16代码单元个数,不是Unicode字符个数。
例如:
const smiley = "?"; // U+1F600,一个码点,但需2个代码单元 console.log(smiley.length); // 输出 2 const text = "a?b"; console.log(text.length); // 输出 4('a'=1, '?'=2, 'b'=1) console.log([...text].length); // 输出 3(使用扩展运算符正确拆分为码点)
正确处理字符(码点)的操作方式
要按实际字符(而非代码单元)遍历、截取或计数,应避免直接使用`.length`、`charAt()`、`charCodeAt()`等基于代码单元的API:
- 用扩展运算符或 `Array.from(str)` 获取码点数组:`[...'???']` → 3个元素(?=1码点,??=1合成码点)
- 用 `String.fromCodePoint()` 创建任意码点字符(支持U+10000以上),替代已废弃的 `String.fromCharCode()`(仅支持BMP)
- 用 `str.codePointAt(i)` 获取位置i起始的码点值(可正确处理代理对),返回整数;`charCodeAt(i)` 只返回单个代码单元值
- 正则匹配时启用 `u` 标志:`/./u` 能正确匹配一个码点,否则默认按代码单元切分
常见误解澄清
❌ “JavaScript用UTF-8” —— 错。引擎内部是UTF-16(源码文件编码可为UTF-8,但运行时字符串始终是UTF-16表示)
❌ “`'\u{1F600}'` 是4字节” —— 错。`\u{...}` 是Unicode转义语法,解析后生成对应码点,存储为2个UTF-16代码单元(共4字节内存),但逻辑上是一个字符
❌ “代理对是‘两个字符’” —— 错。它们在语义上不可分割,`0xD83D 0xDE00` 必须成对出现才代表?;单独出现属于非法编码
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











