在 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
複製代碼
(完)