在 ECMAScript 6 之前,JavaScript 对每个字符都是按照 16 位编码的(UTF-16)处理的。即默认每个字符在计算机底层都是由 16 个 0 和 1 的序列组成。 一个这样的 16 位序列称一个 编码单元(code unit)。
像字符串的 length
属性和 charAt()
方法都是基于 16 位编码单元进行处理的。
但随着 Unicode 字符集 的不断扩展,0x0000
~0xFFFF
这个区间范围,不足以表示所有字符了。这时再使用字符串的 length
属性和 charAt()
方法就存在问题了。
码点
- 码点(Code Points)就是字符编码,用一个数字表示一个字符。
- 码点既能表示
0x0000
~0xFFFF
范围的字符,也能表示> 0xFFFF
范围之外的字符。 - 码点在计算机底层由 1 个或 2 个编码单元组成。
BMP
0x0000
~0xFFFF
区间范围,称为 Basic Multilingual Plane (BMP)。在 BMP 中(包括),一个字符唯一对应一个编码单元(一个 16 位二进制序列)。
BMP 之外的区间称为 supplementary planes。在 supplementary planes 中的每个字符,由 2 个编码单元组成,称 代理对(surrogate pairs)。
- 在
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 位编码序列表示的。
所以,.length
属性值是 2;charAt(0)
和 charAt(1)
其实取的是 "?"
这个字第一个编码单元和第二个编码单元所表示的字符;charCodeAt(0)
更不能取到正确的字符了。
本质上,charAt
和 charCodeAt
后面的数字是表示编码单元的索引值。
从 charCode 到 codePoint
上面的例子里,如果使用 codePointAt()
,就不存在问题了。
var text = "?a";console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97console.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
工具方法:is32Bit
我们可以写一个工具方法,判断一个字符是不是 BMP 之外的字符。
function is32Bit(c) {return c.codePointAt(0) > 0xFFFF;
}console.log(is32Bit("?")); // true
console.log(is32Bit("a")); // false
复制代码
扩展连接
- Universal Character Set characters, from wikipedia.org
- Character Code Charts, from unicode.org
参考链接
- Better Unicode Support, from "Understanding ES6"
(完)