【JS迷你書】String類型與UTF-16

請看一道面試題:javascript

'😂'.length // ?
複製代碼

其結果不是 1,而是 2。😂😂😂html

爲何會是這樣?java

本文主要解決這個問題。git

首先咱們從 Unicode 提及。做爲一個程序員,咱們都應該或多或少了解其相關知識。程序員

世界上有那麼多語言系統,每門語言又有那多文字字符。github

爲了在計算機上表示這些字符,一個自然的想法就是給每一個字符一個編號。把每個字符映射成一個整數,這些數字的學名叫碼位(code point)。好比:面試

'a'.charCodeAt(0// 97
'姚'.charCodeAt(0// 23002
複製代碼

究竟得多少個碼位纔夠呢?剛開始 Unicode 設計人員以爲 2^16 (65536)就該足夠了,因而產生了 UCS-2。(注:事實上 UnicodeUCS 在最開始時不是一家。)app

取 16 次方,即說明 2 個字節數據就能表示一個字符了。這種編碼方式多簡單,不論是從性能仍是從實現上來講,都看起來是一個不錯的選擇。所以,不少語言都採用了16 位編碼的字符串,包括我們的 JavaScriptide

雖然 6 萬多個字符足以包括世界上絕大多數經常使用字符,但事實上仍是不夠的,Unicode 不斷擴展。截止 2019 年 3 月,已收入 150 個書寫系統,共計字符 137928 的。性能

統一用兩個字節來存儲一個字符,這種方式再也不一直有效。所以出現了不一樣的編碼標準,好比 UTF-8UTF-16UTF-32

這裏主要說說與 JS 相關的 UTF-16

UTF-16 是一種變長表示,它對來自經常使用字符 UCS-2 的碼位,仍然用 2 個字節表示。而對來新增很是用的碼位卻用 4 個字節表示。兩者能互相區分開來,這是 UTF-16 的精妙之處所在。

另外須要說明的是,最開始的 2^16 那些數據中並不是都映射滿了。從 U+D800(55296) 到 U+DFFF(57343)共 2048 個碼位,是永久保留的,不映射到任何 Unicode 字符。它的存在爲 UTF-16 提供了方便。

舉例來講,字符😂的碼位是 U+1F602(128514),大於 65535,所以是後添加的字符。

首先用它先減去 65536,獲得 62978,對應的二進制是 1111011000000010

而後左補充 0 至 20 位:00001111011000000010

再從中間切斷成上下兩值:0000111101(61) 和 1000000010(514)。

添加 0xD800(55296)到上值,以造成高位:55296 + 61 = 55357(0xD83D)。

添加 0xDC00(56320)到下值,以造成低位:56320+ 514 = 56834(0xDE02)。

0xD83D0xDE02構成一個代理對,來表示碼位 U+1F602

能夠驗證以下:

'😂'.charCodeAt(0// 55357
'😂'.charCodeAt(1// 56834
'\u{1F602}' // 😂
'\uD83D\uDE02' // 😂
'\u{1F602}'[0] === '\uD83D' // true
複製代碼

此時,想必你也明白了文章開頭的問題了:'😂'.length 之因此爲 2,是由於 JS 至今仍然使用 UCS-2 那種 16 進制讀取方式。

最後,咱們來看一下 JS 規範《Ecma-262 Edition 5.1》 對此的描述:

The String type is the set of all finite ordered sequences of zero or more 16-bit unsigned integer values (「elements」). The String type is generally used to represent textual data in a running ECMAScript program, in which case each element in the String is treated as a code unit value (see Clause 6). Each element is regarded as occupying a position within the sequence. These positions are indexed with nonnegative integers. The first element (if any) is at position 0, the next element (if any) at position 1, and so on. The length of a String is the number of elements (i.e., 16-bit values) within it. The empty String has length zero and therefore contains no elements.


When a String contains actual textual data, each element is considered to be a single UTF-16 code unit. Whether or not this is the actual storage format of a String, the characters within a String are numbered by their initial code unit element position as though they were represented using UTF-16. All operations on Strings (except as otherwise stated) treat them as sequences of undifferentiated 16-bit unsigned integers; they do not ensure the resulting String is in normalised form, nor do they ensure language-sensitive results.

翻譯以下:

字符串類型是由 0 位或 16 位以上無符號整數值(元素)組成的全部有限有序序列的集合。字符串類型一般用於表示運行中的ECMAScript 程序中的文本數據,在這種狀況下,字符串中的每一個元素都被視爲碼元值(參見第6條)。這些位置用非負整數做索引。第一個元素(若是有)位於位置 0,下一個元素(若是有)位於位置 1,以此類推。字符串的長度是元素的數量(即,16位值)。空字符串的長度爲零,所以不包含任何元素。


當字符串包含實際的文本數據時,每一個元素都被認爲是一個單獨的 UTF-16 碼元。不管這是不是字符串的實際存儲格式,字符串中的字符都是經過其初始碼元元素位置進行編號的,就像使用 UTF-16 表示同樣。全部字符串上的操做(除非另有說明)都將它們視爲無差別 16 位無符號整數的序列,它們不能確保獲得的字符串是標準格式的,也不能確保獲得對語言敏感的結果。

其中提到了碼元(code unit),是指最存儲的最小單位,這裏即 2 個字節。

前文討論過,大於 65535 的碼位會生成一個代理對(好比😂的碼位是 U+1F602,代理對是
U+D83DU+DE02),即用了 2 個碼元。上述 JS 規範中明確得指出:「每一個元素都被認爲是一個單獨的 UTF-16 碼元」。所以😂符號爲 2。

本文完。

《JavaScript 迷你書》傳送門,全面夯實基礎

掘金收藏

相關文章
相關標籤/搜索