在 ECMAScript 6 以前,JavaScript 对每一个字符都是按照 16 位编码的(UTF-16)处理的。即默认每一个字符在计算机底层都是由 16 个 0 和 1 的序列组成。 一个这样的 16 位序列称一个 编码单元(code unit)。javascript
像字符串的 length
属性和 charAt()
方法都是基于 16 位编码单元进行处理的。java
但随着 Unicode 字符集 的不断扩展,0x0000
~0xFFFF
这个区间范围,不足以表示全部字符了。这时再使用字符串的 length
属性和 charAt()
方法就存在问题了。es6
0x0000
~0xFFFF
范围的字符,也能表示 > 0xFFFF
范围以外的字符。0x0000
~0xFFFF
区间范围,称为 Basic Multilingual Plane (BMP)。在 BMP 中(包括),一个字符惟一对应一个编码单元(一个 16 位二进制序列)。工具
BMP 以外的区间称为 supplementary planes。在 supplementary planes 中的每一个字符,由 2 个编码单元组成,称 代理对(surrogate pairs)。ui
0x0000
~0xFFFF
区间范围,一个码点等于一个编码单元。> 0xFFFF
区间范围,一个码点等于两个编码单元。在 ECMAScript 5 中,每一个字符都被看作,由一个编码单元组成。那么,在处理 supplementary planes 中的字符时,就有问题了。编码
var text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
复制代码
"𠮷"
在计算机底层由两个编码单元组成,也就是由两个 16 位编码序列组成。而在 .length
属性、charAt()
方法和 charCodeAt()
方法的世界观里,每一个字符都是用一个 16 位编码序列表示的。spa
因此,.length
属性值是 2;charAt(0)
和 charAt(1)
其实取的是 "𠮷"
这个字第一个编码单元和第二个编码单元所表示的字符;charCodeAt(0)
更不能取到正确的字符了。代理
本质上,charAt
和 charCodeAt
后面的数字是表示编码单元的索引值。code
上面的例子里,若是使用 codePointAt()
,就不存在问题了。regexp
var text = "𠮷a";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.log(text.codePointAt(0)); // 134071
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97
复制代码
在 > 0xFFFF
区间范围,字符编码值(char code) 再也不有效,码点依旧有效。因此,咱们要:
String.fromCharCode
迁移到 String.fromCodePoint
string.charCodeAt
迁移到 string.codePointAt
咱们能够写一个工具方法,判断一个字符是否是 BMP 以外的字符。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("𠮷")); // true
console.log(is32Bit("a")); // false
复制代码
(完)