为什么"😀".length === 2
核心原因:UTF-16 代理对(Surrogate Pairs)
JavaScript 采用 UTF-16 编码 存储字符串:
- 基本多文种平面(BMP) :U+0000 到 U+FFFF(占绝大多数常用字符)
- 补充平面(Supplementary Planes) :U+10000 到 U+10FFFF(需要 2 个代码单元表示)
示例:
javascript
const emoji = '😀'; // U+1F600
console.log(emoji.length); // 2
编码机制详解
1. 代理对工作原理
javascript
// 数学公式计算
const codePoint = 0x1F600; // 目标码点
const high = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800; // 高位代理
const low = (codePoint - 0x10000) % 0x400 + 0xDC00; // 低位代理
console.log(high.toString(16)); // d83d(高位代理)
console.log(low.toString(16)); // de00(低位代理)
2. 内存存储结构
字符 | UTF-16 代码单元 | 内存表示(十六进制) |
---|---|---|
😀 | \uD83D\uDE00 | D8 3D DE 00 |
常见场景示例
场景 1:表情符号
javascript
'😂'.length; // 2(U+1F602)
'👨👩👧👦'.length; // 8(包含多个代理对)
场景 2:罕见汉字
javascript
'𠮷'.length; // 2(U+20BB7,日本汉字)
'你'.length; // 2(U+2F804,CJK扩展B汉字)
场景 3:数学符号
javascript
'𝌆'.length; // 2(U+1D306,易经符号)
'𞸀'.length; // 2(U+1EE00,阿拉伯数学符号)
正确获取字符数量的方法
方法 1:使用迭代器
javascript
const str = '😀a';
console.log([...str].length); // 2(正确)
console.log(Array.from(str).length); // 2
方法 2:码点计数
javascript
function countCodePoints(str) {
let count = 0;
for (let i = 0; i < str.length; i++) {
const code = str.codePointAt(i);
if (code > 0xFFFF) i++; // 跳过低位代理
count++;
}
return count;
}
console.log(countCodePoints('😀ab')); // 3
常见陷阱与解决方案
陷阱 1:字符串反转破坏代理对
javascript
// 错误方式
'🚀abc'.split('').reverse().join(''); // 'cba��'
// 正确方式
function reverseString(str) {
return [...str].reverse().join('');
}
reverseString('🚀abc'); // 'cba🚀'
陷阱 2:字符截取不完整
javascript
// 错误截取
'👨👩👧👦'.substring(0, 3); // '👨�'
// 正确截取
function safeSubstring(str, start, end) {
return [...str].slice(start, end).join('');
}
safeSubstring('👨👩👧👦', 0, 2); // '👨👩'
编码检测工具
1. 查看字符的码点
javascript
'😀'.codePointAt(0).toString(16); // '1f600'
2. 检测是否代理对
javascript
function isSurrogatePair(str, index) {
const code = str.charCodeAt(index);
return code >= 0xD800 && code <= 0xDBFF;
}
isSurrogatePair('😀', 0); // true
关键结论
特性 | 说明 |
---|---|
length 属性本质 |
反映 UTF-16 代码单元数量,不是实际字符数 |
代理对字符特征 | Unicode 码点 > 0xFFFF 的字符需要 2 个代码单元 |
正确操作方法 | 使用 [...str] 、codePointAt() 、fromCodePoint() 等现代 API |
性能影响 | 代理对处理会增加计算复杂度,需注意大文本操作的性能 |