什么是代码单元?
JavaScript 中的字符串采用 UTF-16 编码,也就是说,字符串本质上是由一个个 16 位值(16-bits values,称为“代码单元”)组成的序列。
字符串中的每个“元素”从技术上将不是一个“字符”,而是一个“代码单元”。
如果一个字符串中只包含 ASCII 字符,那么每个“代码单元”就对应一个字符。
如果一个字符串还包含非 ASCII 字符,那么这个字符可能是由两个代码单元组成的。这样的话,在操作字符串或调用字符串方法时,可能会得到非预期的结果。
下面这个例子,展示了字符和代码单元的区别:
// JS 中的字符串本质上是由一个个 16 位值组成的序列,
// 而非由字符组成的序列
console.log("😂".length === 2); // true
// 😂
// 名称: 喜极而泣
// 十进制码点:128514
理解代码单元
下面再对代码单元作进一步的深入解释:
- JavaScript 字符串和字符基于 Unicode 编码标准,基于的是 5.1 或之后的版本。
- 每个 Unicode 字符有一个与之对应的整数 ID,称为“码点”。
- Unicode 包括几种编码标准,最出名的是 UTF-8 和 UTF-16。
- 编码,是一套规定如何将一个字符转为位序列(sequence of bits)的标准。
- 根据字符不同,UTF-16 会将每个字符编码为 16 或 32 位存储(每 16 位被看做一个单元,称为“代码单元”)。
- 码点小于 216 的字符,使用 UTF-16 编码的话,需要 16 位的存储内存;码点大于等于 216 的字符,使用 UTF-16 编码的话,需要 32 位的存储内存。
- JavaScript 是这样定义字符串中的“元素”的:采用 UTF-16 编码字符串后的 16 位值序列中的每个 16 位值。首先,是将字符串中的字符编码为一个个位,得到了一个位序列。然后,每 16 个位序列组成一个“代码单元”。索引 0 指代第一个 16 位单元,索引 1 表示第二个 16 位单元,以此类推。这就意味着,如果字符串中包含码点 ≥ 216 的字符时,调用字符串方法得到的结果可能是错误的,因为这里的索引并不是跟字符是一一对应的。
什么是码点?
每个 Unicode 字符都有一个 ID。它是个整数,从 0 开始,这个数字被称为字符码点。

(注意,叫“码点”而不叫“字符ID”的原因,是因为一些“字符”不是真的“字符”,例如空格、回车、制表符、从左到右的标志等等。)
[查阅:JS: 字符与码点的相互转换]
操作哪些字符会出现问题?
回答是码点 ≥216 的字符。其中包含 emoji 和其他一些很少使用的字符,比如:很少使用的中文汉字,或其他古代的语言字符。
[查阅:Unicode Emoji 😄]
码点小于 216 的字符,其码点的十六进制表示只需要 4 个或更少的数字就行了。
再来一些出错的例子
// 我们想要获得第二个字符:大写字母 X
console.log ( "😂X".slice(1)); // 打印 �X
// 错误的
// 😂
// 名称: 喜极而泣
// 十进制码点: 128514
[查阅:JS: String.prototype.slice]
如何遍历字符呢(而非代码单元)?
如果字符串中包含码点 ≥216 的字符,那么使用字符串里的常规的一些方法操作会出现意料之外的错误结果。那么有没有一种方式,能正确遍历出字符串中的一个个字符呢?
有的。使用 ES2015 引入的 for-of 循环就可以。for-of 循环是按照一个一个字符遍历的,而不是 16 位的代码单元。
[查阅:JS: for-of 循环]
真·length:字符串中的字符数量
/* [
xah_string_real_length(str)
返回字符串中的字符数量。
http://xahlee.info/js/js_string_byte_sequence.html
版本 2018-06-17
] */
const xah_string_real_length = (str => {
let i = 0;
for (let c of str) {
i += 1;
}
return i;
});
// --------------------------------------------------
// 测试
console.log ( xah_string_real_length("😂") === 1 ); // true
字符(字符串)到码点(整数)
JS: String.prototype.codePointAt
码点(整数)到字符(字符串)
代码单元(整数)到字符(字符串)
字符(字符串)到代码单元(以长度为 1 的字符串形式)
字符(字符串)到代码单元(以整数形式)
JS: String.prototype.charCodeAt
(完)