为何JavaScript开发者不用string.length而用复杂函数计算字符串长度?
为什么这个函数要手动计算字符串的"字节长度"而不用
str.length? 简单说:str.length返回的是字符串中Unicode代码单元的数量,而这个函数计算的是字符串在UTF-8编码下的字节总长度——这俩完全不是一回事儿!
先搞懂核心区别
在JavaScript里,字符串是用UTF-16编码存储的,每个"代码单元"占2字节,但不同Unicode字符在UTF-8编码下的字节占比不一样:
- 对于ASCII字符(U+0000到U+007F):UTF-8编码占1字节,和
str.length的计数一致 - 对于U+0080到U+07FF的字符(比如部分欧洲语言特殊字符):UTF-8编码占2字节,
str.length算1个单元,所以要额外加1 - 对于U+0800到U+FFFF的字符(比如中文、日文):UTF-8编码占3字节,
str.length算1个单元,所以要额外加2 - 对于U+10000到U+10FFFF的字符(比如大部分emoji):JS里用代理对(两个UTF-16代码单元)表示,
str.length算2,但UTF-8编码占4字节,这时候要特殊处理避免重复计数
拆解这个函数的逻辑
function byteLength(str) { // returns the byte length of an utf8 string var s = str.length; for (var i=str.length-1; i>=0; i--) { var code = str.charCodeAt(i); if (code > 0x7f && code <= 0x7ff) s++; else if (code > 0x7ff && code <= 0xffff) s+=2; if (code >= 0xDC00 && code <= 0xDFFF) i--; //trail surrogate } return s; }
咱们一步步看:
- 初始值
s = str.length:先拿JS内部的代码单元数量当基础计数 - 从后往前遍历每个字符的编码:
- 如果字符是U+0080到U+07FF之间的:UTF-8比代码单元多占1字节,所以
s++ - 如果字符是U+0800到U+FFFF之间的:UTF-8比代码单元多占2字节,所以
s+=2 - 如果遇到低代理字符(
0xDC00到0xDFFF):这是代理对的后半部分,对应的前半部分(高代理)已经被处理过了,所以i--跳过它,避免重复计算代理对的字节数
- 如果字符是U+0080到U+07FF之间的:UTF-8比代码单元多占1字节,所以
举个实际例子对比
- 普通ASCII字符
"a":str.length=1,函数返回1(UTF-8占1字节,正确) - 中文
"中":str.length=1,函数返回3(UTF-8编码确实是3字节,正确) - Emoji
"😀":str.length=2(代理对),函数返回4(UTF-8编码占4字节,正确)
如果直接用str.length,上面后两个例子的结果就完全不对了——这就是为什么开发者要写这个函数:他们需要的是字符串转成UTF-8后的实际字节数,而不是JS内部存储的代码单元数量。
内容的提问来源于stack exchange,提问作者Diagathe Josué
相关产品推荐
相关产品推荐

