Unicode與JavaScript詳解

來源:酷勤網(wǎng)


一浪感、Unicode是什么更卒?
Unicode源于一個(gè)很簡單的想法:將全世界所有的字符包含在一個(gè)集合里少梁,計(jì)算機(jī)只要支持這一個(gè)字符集洛口,就能顯示所有的字符,再也不會(huì)有亂碼了凯沪。



它從0開始第焰,為每個(gè)符號(hào)指定一個(gè)編號(hào),這叫做"碼點(diǎn)"(code point)妨马。比如挺举,碼點(diǎn)0的符號(hào)就是null(表示所有二進(jìn)制位都是0)。

U+0000 = null

上式中烘跺,U+表示緊跟在后面的十六進(jìn)制數(shù)是Unicode的碼點(diǎn)湘纵。



目前,Unicode的最新版本是7.0版滤淳,一共收入了109449個(gè)符號(hào)梧喷,其中的中日韓文字為74500個(gè)。可以近似認(rèn)為铺敌,全世界現(xiàn)有的符號(hào)當(dāng)中汇歹,三分之二以上來自東亞文字。比如偿凭,中文"好"的碼點(diǎn)是十六進(jìn)制的597D产弹。

U+597D = 好

這么多符號(hào),Unicode不是一次性定義的弯囊,而是分區(qū)定義痰哨。每個(gè)區(qū)可以存放65536個(gè)(216)字符,稱為一個(gè)平面(plane)匾嘱。目前斤斧,一共有17個(gè)(25)平面,也就是說奄毡,整個(gè)Unicode字符集的大小現(xiàn)在是2^21。
最前面的65536個(gè)字符位贝或,稱為基本平面(縮寫B(tài)MP)吼过,它的碼點(diǎn)范圍是從0一直到216
-1,寫成16進(jìn)制就是從U+0000到U+FFFF咪奖。所有最常見的字符都放在這個(gè)平面盗忱,這是Unicode最先定義和公布的一個(gè)平面。
剩下的字符都放在輔助平面(縮寫SMP)羊赵,碼點(diǎn)范圍從U+010000一直到U+10FFFF趟佃。


二、UTF-32與UTF-8
Unicode只規(guī)定了每個(gè)字符的碼點(diǎn)昧捷,到底用什么樣的字節(jié)序表示這個(gè)碼點(diǎn)闲昭,就涉及到編碼方法。
最直觀的編碼方法是靡挥,每個(gè)碼點(diǎn)使用四個(gè)字節(jié)表示序矩,字節(jié)內(nèi)容一一對(duì)應(yīng)碼點(diǎn)。這種編碼方法就叫做UTF-32跋破。比如簸淀,碼點(diǎn)0就用四個(gè)字節(jié)的0表示,碼點(diǎn)597D就在前面加兩個(gè)字節(jié)的0毒返。

U+0000 = 0x0000 0000 U+597D = 0x0000 597D


UTF-32的優(yōu)點(diǎn)在于租幕,轉(zhuǎn)換規(guī)則簡單直觀,查找效率高拧簸。缺點(diǎn)在于浪費(fèi)空間劲绪,同樣內(nèi)容的英語文本,它會(huì)比ASCII編碼大四倍。這個(gè)缺點(diǎn)很致命珠叔,導(dǎo)致實(shí)際上沒有人使用這種編碼方法蝎宇,HTML 5標(biāo)準(zhǔn)就明文規(guī)定,網(wǎng)頁不得編碼成UTF-32祷安。

人們真正需要的是一種節(jié)省空間的編碼方法姥芥,這導(dǎo)致了UTF-8的誕生。UTF-8是一種變長的編碼方法汇鞭,字符長度從1個(gè)字節(jié)到4個(gè)字節(jié)不等凉唐。越是常用的字符,字節(jié)越短霍骄,最前面的128個(gè)字符台囱,只使用1個(gè)字節(jié)表示,與ASCII碼完全相同读整。

由于UTF-8這種節(jié)省空間的特性簿训,導(dǎo)致它成為互聯(lián)網(wǎng)上最常見的網(wǎng)頁編碼。不過米间,它跟今天的主題關(guān)系不大强品,我就不深入了,具體的轉(zhuǎn)碼方法屈糊,可以參考我多年前寫的《字符編碼筆記》的榛。
三、UTF-16簡介
UTF-16編碼介于UTF-32與UTF-8之間逻锐,同時(shí)結(jié)合了定長和變長兩種編碼方法的特點(diǎn)夫晌。
它的編碼規(guī)則很簡單:基本平面的字符占用2個(gè)字節(jié),輔助平面的字符占用4個(gè)字節(jié)昧诱。也就是說晓淀,UTF-16的編碼長度要么是2個(gè)字節(jié)(U+0000到U+FFFF),要么是4個(gè)字節(jié)(U+010000到U+10FFFF)盏档。

于是就有一個(gè)問題要糊,當(dāng)我們遇到兩個(gè)字節(jié),怎么看出它本身是一個(gè)字符妆丘,還是需要跟其他兩個(gè)字節(jié)放在一起解讀锄俄?
說來很巧妙,我也不知道是不是故意的設(shè)計(jì)勺拣,在基本平面內(nèi)奶赠,從U+D800到U+DFFF是一個(gè)空段,即這些碼點(diǎn)不對(duì)應(yīng)任何字符药有。因此毅戈,這個(gè)空段可以用來映射輔助平面的字符苹丸。
具體來說,輔助平面的字符位共有220
個(gè)苇经,也就是說赘理,對(duì)應(yīng)這些字符至少需要20個(gè)二進(jìn)制位。UTF-16將這20位拆成兩半扇单,前10位映射在U+D800到U+DBFF(空間大小210
)商模,稱為高位(H),后10位映射在U+DC00到U+DFFF(空間大小210
)蜘澜,稱為低位(L)施流。這意味著,一個(gè)輔助平面的字符鄙信,被拆成兩個(gè)基本平面的字符表示瞪醋。

所以,當(dāng)我們遇到兩個(gè)字節(jié)装诡,發(fā)現(xiàn)它的碼點(diǎn)在U+D800到U+DBFF之間银受,就可以斷定,緊跟在后面的兩個(gè)字節(jié)的碼點(diǎn)鸦采,應(yīng)該在U+DC00到U+DFFF之間宾巍,這四個(gè)字節(jié)必須放在一起解讀。
四赖淤、UTF-16的轉(zhuǎn)碼公式
Unicode碼點(diǎn)轉(zhuǎn)成UTF-16的時(shí)候蜀漆,首先區(qū)分這是基本平面字符谅河,還是輔助平面字符咱旱。如果是前者,直接將碼點(diǎn)轉(zhuǎn)為對(duì)應(yīng)的十六進(jìn)制形式绷耍,長度為兩字節(jié)吐限。

U+597D = 0x597D

如果是輔助平面字符,Unicode 3.0版給出了轉(zhuǎn)碼公式褂始。

H = Math.floor((c-0x10000) / 0x400)+0xD800 L = (c - 0x10000) % 0x400 + 0xDC00

以字符

為例诸典,它是一個(gè)輔助平面字符,碼點(diǎn)為U+1D306崎苗,將其轉(zhuǎn)為UTF-16的計(jì)算過程如下狐粱。

H = Math.floor((0x1D306-0x10000)/0x400)+0xD800 = 0xD834 L = (0x1D306-0x10000) % 0x400+0xDC00 = 0xDF06

所以,字符

的UTF-16編碼就是0xD834 DF06胆数,長度為四個(gè)字節(jié)肌蜻。

五、JavaScript使用哪一種編碼必尼?

JavaScript語言采用Unicode字符集蒋搜,但是只支持一種編碼方法篡撵。
這種編碼既不是UTF-16,也不是UTF-8豆挽,更不是UTF-32育谬。上面那些編碼方法,JavaScript都不用帮哈。
JavaScript用的是UCS-2膛檀!

六、UCS-2編碼
怎么突然殺出一個(gè)UCS-2但汞?這就需要講一點(diǎn)歷史宿刮。
互聯(lián)網(wǎng)還沒出現(xiàn)的年代,曾經(jīng)有兩個(gè)團(tuán)隊(duì)私蕾,不約而同想搞統(tǒng)一字符集僵缺。一個(gè)是1988年成立的UCS團(tuán)隊(duì),另一個(gè)是1989年成立的Unicode團(tuán)隊(duì)踩叭。等到他們發(fā)現(xiàn)了對(duì)方的存在磕潮,很快就達(dá)成一致:世界上不需要兩套統(tǒng)一字符集。
1991年10月容贝,兩個(gè)團(tuán)隊(duì)決定合并字符集自脯。也就是說,從今以后只發(fā)布一套字符集斤富,就是Unicode膏潮,并且修訂此前發(fā)布的字符集,UCS的碼點(diǎn)將與Unicode完全一致满力。

UCS的開發(fā)進(jìn)度快于Unicode焕参,1990年就公布了第一套編碼方法UCS-2,使用2個(gè)字節(jié)表示已經(jīng)有碼點(diǎn)的字符油额。(那個(gè)時(shí)候只有一個(gè)平面叠纷,就是基本平面,所以2個(gè)字節(jié)就夠用了潦嘶。)UTF-16編碼遲至1996年7月才公布涩嚣,明確宣布是UCS-2的超集,即基本平面字符沿用UCS-2編碼掂僵,輔助平面字符定義了4個(gè)字節(jié)的表示方法航厚。
兩者的關(guān)系簡單說,就是UTF-16取代了UCS-2锰蓬,或者說UCS-2整合進(jìn)了UTF-16幔睬。所以,現(xiàn)在只有UTF-16互妓,沒有UCS-2溪窒。
七坤塞、JavaScript的誕生背景
那么,為什么JavaScript不選擇更高級(jí)的UTF-16澈蚌,而用了已經(jīng)被淘汰的UCS-2呢摹芙?
答案很簡單:非不想也,是不能也宛瞄。因?yàn)樵贘avaScript語言出現(xiàn)的時(shí)候浮禾,還沒有UTF-16編碼。
1995年5月份汗,Brendan Eich用了10天設(shè)計(jì)了JavaScript語言盈电;10月,第一個(gè)解釋引擎問世杯活;次年11月匆帚,Netscape正式向ECMA提交語言標(biāo)準(zhǔn)(整個(gè)過程詳見《JavaScript誕生記》)。對(duì)比UTF-16的發(fā)布時(shí)間(1996年7月)旁钧,就會(huì)明白Netscape公司那時(shí)沒有其他選擇吸重,只有UCS-2一種編碼方法可用!

八歪今、JavaScript字符函數(shù)的局限
由于JavaScript只能處理UCS-2編碼嚎幸,造成所有字符在這門語言中都是2個(gè)字節(jié),如果是4個(gè)字節(jié)的字符寄猩,會(huì)當(dāng)作兩個(gè)雙字節(jié)的字符處理嫉晶。JavaScript的字符函數(shù)都受到這一點(diǎn)的影響,無法返回正確結(jié)果田篇。

還是以字符
為例替废,它的UTF-16編碼是4個(gè)字節(jié)的0xD834 DF06。問題就來了斯辰,4個(gè)字節(jié)的編碼不屬于UCS-2舶担,JavaScript不認(rèn)識(shí)坡疼,只會(huì)把它看作單獨(dú)的兩個(gè)字符U+D834和U+DF06彬呻。前面說過,這兩個(gè)碼點(diǎn)是空的柄瑰,所以JavaScript會(huì)認(rèn)為
是兩個(gè)空字符組成的字符串闸氮!

上面代碼表示,JavaScript認(rèn)為字符
的長度是2教沾,取到的第一個(gè)字符是空字符蒲跨,取到的第一個(gè)字符的碼點(diǎn)是0xDB34。這些結(jié)果都不正確授翻!

解決這個(gè)問題或悲,必須對(duì)碼點(diǎn)做一個(gè)判斷孙咪,然后手動(dòng)調(diào)整。下面是正確的遍歷字符串的寫法巡语。

while (++index < length) { // ... if (charCode >= 0xD800 && charCode <= 0xDBFF) { output.push(character + string.charAt(++index)); } else { output.push(character); } }

上面代碼表示翎蹈,遍歷字符串的時(shí)候,必須對(duì)碼點(diǎn)做一個(gè)判斷男公,只要落在0xD800到0xDBFF的區(qū)間荤堪,就要連同后面2個(gè)字節(jié)一起讀取。
類似的問題存在于所有的JavaScript字符操作函數(shù)枢赔。

String.prototype.replace()
String.prototype.substring()
String.prototype.slice()
...

上面的函數(shù)都只對(duì)2字節(jié)的碼點(diǎn)有效澄阳。要正確處理4字節(jié)的碼點(diǎn),就必須逐一部署自己的版本踏拜,判斷一下當(dāng)前字符的碼點(diǎn)范圍碎赢。
九、ECMAScript 6



JavaScript的下一個(gè)版本ECMAScript 6(簡稱ES6)速梗,大幅增強(qiáng)了Unicode支持揩抡,基本上解決了這個(gè)問題。
(1)正確識(shí)別字符
ES6可以自動(dòng)識(shí)別4字節(jié)的碼點(diǎn)镀琉。因此峦嗤,遍歷字符串就簡單多了。

for (let s of string ) { // ... }

但是屋摔,為了保持兼容烁设,length屬性還是原來的行為方式。為了得到字符串的正確長度钓试,可以用下面的方式装黑。

Array.from(string).length

(2)碼點(diǎn)表示法
JavaScript允許直接用碼點(diǎn)表示Unicode字符,寫法是"斜杠+u+碼點(diǎn)"弓熏。

'好' === 'u597D' // true

但是恋谭,這種表示法對(duì)4字節(jié)的碼點(diǎn)無效。ES6修正了這個(gè)問題挽鞠,只要將碼點(diǎn)放在大括號(hào)內(nèi)疚颊,就能正確識(shí)別。



(3)字符串處理函數(shù)
ES6新增了幾個(gè)專門處理4字節(jié)碼點(diǎn)的函數(shù)信认。

String.fromCodePoint():從Unicode碼點(diǎn)返回對(duì)應(yīng)字符
String.prototype.codePointAt():從字符返回對(duì)應(yīng)的碼點(diǎn)
String.prototype.at():返回字符串給定位置的字符

(4)正則表達(dá)式
ES6提供了u修飾符材义,對(duì)正則表達(dá)式添加4字節(jié)碼點(diǎn)的支持。


(5)Unicode正規(guī)化
有些字符除了字母以外嫁赏,還有附加符號(hào)其掂。比如,漢語拼音的瑯??????????????????????¥﹤????????耦????????????????獨(dú)恧???/p>

Unicode提供了兩種表示方法潦蝇。一種是帶附加符號(hào)的單個(gè)字符款熬,即一個(gè)碼點(diǎn)表示一個(gè)字符深寥,比如瑧??g???+01D1;另一種是將附加符號(hào)單獨(dú)作為一個(gè)碼點(diǎn)贤牛,與主體字符復(fù)合顯示翩迈,即兩個(gè)碼點(diǎn)表示一個(gè)字符,比如瑥???晦萏(U+004F) + ???+030C)盔夜。

// 方法一 'u01D1' // '瑧 // 方法二 'u004Fu030C' // '瑧

這兩種表示方法负饲,視覺和語義都完全一樣,理應(yīng)作為等同情況處理喂链。但是返十,JavaScript無法辨別。

'u01D1'==='u004Fu030C' //false

ES6提供了normalize方法椭微,允許"Unicode正規(guī)化"洞坑,即將兩種方法轉(zhuǎn)為同樣的序列。

'u01D1'.normalize() === 'u004Fu030C'.normalize() // true

關(guān)于ES6的更多介紹蝇率,請(qǐng)看《ECMAScript 6入門》迟杂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市本慕,隨后出現(xiàn)的幾起案子排拷,更是在濱河造成了極大的恐慌,老刑警劉巖锅尘,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件监氢,死亡現(xiàn)場離奇詭異,居然都是意外死亡藤违,警方通過查閱死者的電腦和手機(jī)浪腐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顿乒,“玉大人议街,你說我怎么就攤上這事¤甸” “怎么了特漩?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犹菱。 經(jīng)常有香客問我拾稳,道長吮炕,這世上最難降的妖魔是什么腊脱? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮龙亲,結(jié)果婚禮上陕凹,老公的妹妹穿的比我還像新娘悍抑。我一直安慰自己,他們只是感情好杜耙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布搜骡。 她就那樣靜靜地躺著,像睡著了一般佑女。 火紅的嫁衣襯著肌膚如雪记靡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天团驱,我揣著相機(jī)與錄音摸吠,去河邊找鬼。 笑死嚎花,一個(gè)胖子當(dāng)著我的面吹牛寸痢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播紊选,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼啼止,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兵罢?” 一聲冷哼從身側(cè)響起献烦,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卖词,沒想到半個(gè)月后仿荆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坏平,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拢操,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舶替。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡令境,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顾瞪,到底是詐尸還是另有隱情舔庶,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布陈醒,位于F島的核電站惕橙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钉跷。R本人自食惡果不足惜弥鹦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彬坏,春花似錦朦促、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幻赚,卻和暖如春禀忆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背落恼。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工油湖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人领跛。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓乏德,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吠昭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喊括,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 摘要:本文從Unicode入手郑什,介紹由于通信問題而產(chǎn)生的字符集,以及Unicode的發(fā)展情況蒲肋。介紹各種字符集的及其...
    瘋狂的冰塊閱讀 1,918評(píng)論 0 6
  • 字符編碼的問題蘑拯,每個(gè)程序員都會(huì)遇到,深入探索其背后的原理和機(jī)制兜粘,能讓我們少走很多彎路申窘。 1、歷史簡述 Unicod...
    1angxi閱讀 13,227評(píng)論 0 7
  • 轉(zhuǎn)載自O(shè)bjeC中國 歷史 計(jì)算機(jī)沒法直接處理文本孔轴,它只和數(shù)字打交道墩剖。為了在計(jì)算機(jī)里用數(shù)字表示文本掉奄,我們指定了一個(gè)...
    玉米包谷閱讀 1,164評(píng)論 0 4
  • 畢業(yè)三年苍苞,換了10份工作盏筐,在職場中,有的是和領(lǐng)導(dǎo)關(guān)系相處不好晋柱,有的是覺得環(huán)境太壓抑优构,或者自己不喜歡工作方式等等,換...
    晴空依然閱讀 178評(píng)論 0 2
  • 百花園 回眸間 微風(fēng)撫動(dòng)發(fā)帶如仙 探人間 入眼簾 一身戎裝笑容璀璨 潑文灑墨猶似天作合 舞刀弄槍一壺濁酒澀 尚不知...
    丁香枝上閱讀 368評(píng)論 3 5