JavaScript 的 length 属性返回 UTF-16 编码单元数而非字符数,因代理对机制导致 emoji 等字符被计为多个单元;获取真实字符数应使用 Array.from(str).length 或 Intl.Segmenter。

JavaScript 中 length 属性返回字符串的 UTF-16 编码单元(code unit)数量,不是字符数。这意味着对某些特殊字符(如 emoji、中文、带变音符号的字母等),length 可能与“肉眼看到的字符个数”不一致。
为什么 length 会“算多”?——UTF-16 代理对机制
JavaScript 字符串基于 UTF-16 编码。Unicode 中编号大于 0xFFFF 的字符(如大多数 emoji、部分罕见汉字、古文字等)在 UTF-16 中需要用两个 16 位编码单元表示,称为“代理对(surrogate pair)”。length 会把这两个单元都算作独立的“位置”,因此返回值比实际字符数大 1。
-
'??'.length→ 返回 4(实际是 1 个组合 emoji,由 4 个 UTF-16 单元组成) -
'?'.length→ 返回 2(这个汉字 Unicode 码点为U+20BB7,需代理对表示) -
'a'.length→ 返回 1(基本拉丁字母,单个 UTF-16 单元)
如何获取真正的字符数量?
要得到人类感知的“字符个数”,不能只依赖 length,推荐以下方法:
-
使用扩展运算符 + Array.from():
Array.from(str).length或[...str].length—— 利用 ES6 对字符串的迭代器支持,正确识别代理对和组合字符 -
使用 Intl.Segmenter(现代浏览器):
new Intl.Segmenter().segment(str).toArray().length—— 更精准,还能处理组合标记(如带重音的é)、ZWJ 连接符(如家庭 emoji ????)等 -
避免正则 /./g 全局匹配:它在非 Unicode 模式下也会被代理对干扰;如需正则,务必加
u标志,例如/./gu
常见易错场景提醒
这些地方若盲目用 length,容易出 bug:
立即学习“Java免费学习笔记(深入)”;
-
截取前 N 个“字符”:用
str.substring(0, n)基于 code unit 位置,可能在代理对中间截断,导致乱码(如显示 );应改用Array.from(str).slice(0, n).join('') -
表单字数限制(含 emoji):用户输入 ?✨?,
length是 6,但用户认为只有 3 个字符;建议前端校验用[...str].length,后端也需统一逻辑 - 遍历字符串用 for (let i = 0; i :可能把一个 emoji 当成多个“字符”分别访问;推荐用
for (const char of str)或Array.from(str).forEach()
中英文混合与变音符号的兼容性
除 emoji 外,其他情况也要注意:
-
'café'.length→ 5(e 上的尖音符是独立组合字符\u0301,共 5 个 code unit) -
'你好'.length→ 2(常用汉字都在 BMP 平面,每个占 1 个 code unit,所以 length 准确) -
'???'.length→ 7(基础 emoji + 修饰符 + ZWJ + emoji,多个 code unit 组合)
不复杂但容易忽略:只要涉及用户可见的“字符计数”或“按字符操作”,就别直接信 length。










