天天看點

編碼 unicode 及其在 javascript 中的使用編碼 unicode 及其在 javascript 中的使用

計算機使用 8 位(bit)二進制表示一個位元組(byte),計算機記憶體最小尋址機關就是 1 位元組。早期為了在計算機上使用同一的方式使用字元,使用無符号整數來标記字元。

ansi(美國國家标準局)制訂了ascii(american standard code for information interchange,美國資訊交換标準代碼),使用一個位元組大小的二進制數來編碼每個字元。ascii已經被國際标準化組織(iso)定為國際标準,稱為iso 646标準。

一個位元組為8位二進制,2的8次方為256,是以有256個字元可以用一個位元組來表示(0~255),但ascii字元集隻設計了128個字元(字母、數字、一些标點符号和控制字元),是以實際上隻用到7位二進制,第八位設定為0,剩下的128個編碼位置是閑置的。

有的計算機廠商可能會利用閑置的128個空位來制訂一些字元的編碼,稱為oem字元集。例如,ibm使用多出來的128位擴充了一個ascii 擴充表,包含了一些控制符和制表符等等,被廣泛使用在電子元件的資料通訊和存儲中,但oem字元集不是通用的标準。

為了編碼更多的字元,2個研究字元編碼的機構合并研究成果,制訂了 unicode 字元集。unicode 字元集使用使用多個位元組來為字元編碼,按使用的位元組數不同制訂了不同方案,所有 unicode 編碼方案前 1 個位元組(256個碼位)的編碼對應的字元都是 ascii 字元集中的字元。

目前 unicode 編碼已經達到 64 位,使用 8 個位元組标記一個字元。

如果每個字元用2個位元組(16位二進制數)來标記,可以編碼 65536 個字元(2 的16次方),這基本上已經可以标記世界上所有國家的語言符号,是以,在實際中通用的是ucs-2通用字元集(universal character set,ucs),由iso制定的iso 10646(或稱iso/iec 10646)标準所定義。ucs 為第一位元組的128個空位增補了一個字元集,稱為 c1控制符及拉丁文補充-1 (c1 control and latin 1 supplement)。

ucs-2字元集編碼法有17個位面,每個位面都用2個位元組來标記字元,17個位面可以映射 1,112,064個字元,其中最常用最重要的是編号為 0 的位面,裡面包含了最常用的字元編碼,稱為基本多國語言平面bmp(basic multilingual plane)。

unicode 第 0 平面(bmp)中的編碼被劃分為不同區段,各國文字元号、控制符、制表符、圖形字元等 都有連續的分布,其中中文簡繁體區段是 4e00-9fbf。

0000-007f:c0控制符及基本拉丁文 (c0 control and basic latin)  

0080-00ff:c1控制符及拉丁文補充-1 (c1 control and latin 1 supplement)  

0100-017f:拉丁文擴充-a (latin extended-a)  

0180-024f:拉丁文擴充-b (latin extended-b)  

0250-02af:國際音标擴充 (ipa extensions)  

02b0-02ff:空白修飾字母 (spacing modifiers)  

0300-036f:結合用讀音符号 (combining diacritics marks)

4e00-9fbf:cjk 統一表意符号 (cjk unified ideographs)

(......還有很多國家語言和甲骨文等已經不用或極少使用的語言或字元的專用區段)

從第1位面開始,字元的unicode編碼已經超出16位二進制數的範圍,是以ucs-2無法使用2個位元組直接編碼bmp位面之外的字元。但是,在 ucs-2 編碼中,區段 ud800 到 udfff 的碼位是閑置的保留位,是以,可以使用這個區段中的碼位通過一定的轉換方式映射到其他位面的 unicode 編碼。

在實際的字元傳輸和存儲行為中,為了節省位元組數,可能不會直接傳輸 unicode 編碼,而是使用 unicode轉換格式(unicode transformation format,簡稱為utf),目前常見的 utf格式有utf-7, utf-7.5, utf-8, utf-16, 以及 utf-32,他們是由 ittf(internet engineering task force,網際網路工程任務組)組織進行标準化的,utf-8 和 utf-16 編碼使用比較廣泛。

utf-16 編碼:該編碼法在 ucs-2 第0位面字元集的基礎上利用 d800-dfff 區段的碼位通過一定轉換方式将超出2位元組的字元編碼為一對16比特長的碼元(即32bit,4bytes),稱作代理碼對 (surrogate pair)。

編碼 unicode 及其在 javascript 中的使用編碼 unicode 及其在 javascript 中的使用

例如字元“

編碼 unicode 及其在 javascript 中的使用編碼 unicode 及其在 javascript 中的使用

”,他處于編号'2'的位面(總共17個位面,位面編号為16進制數0-10,第0位面可以舍去編号0直接用4位16進制數編碼),碼位是a6a5,即unicode編碼為 2a6a5,它在utf-16中的代理碼對為 d869 dea5,但是通過 js 的charcodeat()函數隻能得出高位碼對,但是這并不影響解碼軟體對字元編碼進行定位,因為這些字元的代理碼對都是成對地分布在  ud800-udfff 區段内的,并不存在交叉的現象,知道高位碼對也可以簡單地搜尋到低位碼對。

編碼 unicode 及其在 javascript 中的使用編碼 unicode 及其在 javascript 中的使用

utf-16 編碼出現以前,ud800-udfff 區段的碼位可能會被一些計算機産品設計者利用,而且其他位面的字元極少用到,是以,一些軟體可能無法正确識别代理碼對,這可能會導緻一些bug。例如,python 2.6 在 unix 平台上便無法正确識别代理碼對。如果一個軟體聲稱自己支援ucs-2,那麼他很可能是不支援utf-16的。

javascript 跟 java 一樣使用utf-16編碼,是以, 實際上 javascript 程式中變量名和函數名可以使用ascii 之外的字元,例如中文,不過網頁檔案儲存的編碼格式要注意,使用的編碼格式對字元編碼的範圍應當不小于 utf-16,比如儲存為 utf-8 編碼。

function 試試看(){

var 打個招呼 = {你好:'好你妹!'};

alert(打個招呼.你好);

}

試試看();

<a target="_blank"></a>

unicode 16 使用 16位二進制編碼字元,但是其編碼格式在書面上使用16進制(二進制寫起來太長了),在javascript中, \u 加 4個16進制字元表示一個字元的編碼(每個位元組 8 位二進制對應2位十六進制,2^8 = 256 = 16^2),不足4位16進制的,高位用0補足,比如 \u55b5 表示漢字 "喵",字母 "a" 的 ascii 碼是10進制 97,表示成 16 進制 unicode 編碼格式就是 \u0061。試試列印出來: 

document.write('\u55b5'); 

document.write('\u0061');

在 javascript 字元串的 charcodeat 和 string.fromchartcode 中取得和使用的位元組編碼都是 10 進制的,是以在 document.write 和 這些方法配合使用時需要進行進制轉換。

另外要注意的是,如果一個變量儲存了一個字元的 unicode() 編碼,你想用 document.write() 列印到頁面上就需要注意了,不要将'\u' 轉義成 '\\u' ,如果轉義了,需要使用 eval() 來執行,否則将直接把編碼列印出來:

var code1 = '\\u0061';

document.write(code1); // \u0061

var code2 = '\u0061';

document.write(code2); // a

但是在表達式中,也許你想拼接出 unicode 編碼後列印字元串,這就要注意了,因為在字元串中 \u 後面必須接 4個十六進制字元才是合法的文法,是以不得不轉義:

var code = "\\u"+("0000"+('a').charcodeat(0).tostring(16)).slice(-4);

document.write(code); // \u0061

document.write(eval('"'+code+'"')); //正确做法 ,注意eval 時加上引号,因為 document.write 接受的參數是字元串,document.write("\u0061"),其中 \u0061 是單個字元,而不是可以分割的多個字元組成的字元串 ( "\\"+"u"+"0061" ), 而形如document.write(\u0061) 的語句是個文法錯誤.

 試試下面代碼

function \u0061(){ console.log(123) }

\u0061();

a();

在 js 中,可以使用 \x 加 2位16進制字元标記一個單位元組的字元,例如字元 'a' 可以表示為 \x61,用法類似\u 加4位16進制編碼。

document.write('\x61');// a

\u 加四位十六進制數 或 \x 加2位十六進制數屬于轉義字元,在 js 字元串長度中隻算 1 個,轉義字元不能直接用于 html 檔案(不會轉換後輸出,而是直接輸出轉義格式的字元串),但可以用 document.write()列印出來,因為在 js 文法範圍内,雖然表達方式不一樣,但是轉義字元和直接的字元字面量都是指同一個東西:

console.log("\u0061"==='a'); //true

('123|u55b5abc').length //12

('123|u55b5abc').split('') //["1", "2", "3", "|", "u", "5", "5", "b", "5", "a", "b", "c"]

('123\u55b5abc').length //7

('123\u55b5abc').split('') // ["1", "2", "3", "喵", "a", "b", "c"]

當向頁面顯示使用者輸入的内容是,通常我們要過濾或轉義 script 标簽以避免 xss 攻擊,

但是,需要注意的是, \u+16進制 、\x+16進制 、字元實體如果出現在模闆中的 html 标簽屬性中就需要特别注意了,他們會正确解碼後才賦給标簽的屬性,例如:

document.body.innerhtml = '&lt;img src="wrongurl.gif" onerror="&amp;#116;&amp;#104;&amp;#105;&amp;#115;&amp;#46;&amp;#115;&amp;#114;&amp;#99;&amp;#61;&amp;#39;&amp;#104;&amp;#116;&amp;#116;&amp;#112;&amp;#58;&amp;#47;&amp;#47;&amp;#119;&amp;#119;&amp;#119;&amp;#46;&amp;#120;&amp;#115;&amp;#115;&amp;#46;&amp;#99;&amp;#111;&amp;#109;&amp;#39;" &gt;';

對标簽屬性之進行轉義時,需要對注意,是比較下面2個做法:

document.body.innerhtml = '&lt;a onclick="\&amp;\#116;\&amp;\#104;\&amp;\#105;\&amp;\#115;"&gt;click me&lt;/a&gt;';

document.body.innerhtml = '&lt;a onclick="\\&amp;\\#116;\\&amp;\\#104;\\&amp;\\#105;\\&amp;\\#115;"&gt;click me&lt;/a&gt;';

5、八進制轉義字元

js 字元串中,\ 開始後接正數,可能被解析為8進制轉義字元。

一個八進制轉義字元形成的條件是:斜線後面接的整數最長3位,至少1位,單個數字的字面值不大于8。

如果過不滿足任意一項,都将結束一個字元的解釋,開始新字元的解釋。八進制轉義字元的10進制數字字面值最大值為377,即 '\377' 被解析為一個字元,'\378' 被解析為2個字元: '\37' 和 '8' :

'\377'.length //1

console.log('\377') // ÿ

'\378'.length //2

'\128'.length //2

console.log('\127') //w

'w'.charcodeat(0) //87

(87).tostring(8) //'127'

由于 unicode 編碼的前 128 位是ascii 碼,他們已經包含大部分西歐語言的字元,而且隻要一個位元組就可以全部編号了,還多出來 128 個空位,8位2進制,其實隻使用了7位,第8位被設定為0,事實上,電子郵件使用的base64編碼就認為第8位是1的的字元編碼都是傳輸錯誤。

對于西歐語言來說,所有字元都使用 2個位元組來标記太浪費計算機記憶體了,于是有了utf-7、utf-8 等變長編碼方式,其中 utf-8 又稱萬國碼,使用最廣泛,現在已經标準化為rfc 3629。

utf-8的編碼規則有二條:

1) 對于單位元組的符号,位元組的第一位設為0,後面7位為這個符号的unicode碼。是以對于英語字母,utf-8編碼和ascii碼是相同的。

2) 對于n位元組的符号(n&gt;1),第一個位元組的前n位都設為1,第n+1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符号的unicode 二進制碼,如果空位多于實際的unicode 二進制碼位,在高位補0。

因為多位元組字元編碼要使用前置标記位,uft-8 對漢字的編碼可能會超出2個位元組達到3個位元組,更少用的字元可能達到4個位元組的編碼。

下表是utf-8 編碼的碼位分布情況,其中的 x 表示字元的 unicode 二進制編碼unicode符号範圍 (十六進制) | utf-8編碼方式 | 二進制)

---------------------------------------------------------------------

0000 0000-0000 007f | 0xxxxxxx

0000 0080-0000 07ff | 110xxxxx 10xxxxxx

0000 0800-0000 ffff | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 ffff | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

例如,漢字 '喵' 的utf-8編碼十六進制格式是 e596b5,把他們轉換二進制:

parseint('e5',16).tostring(2) // 11100101

parseint('96',16).tostring(2) // 10010110

parseint('b5',16).tostring(2) // 10110101

是以 它的 utf-8 二進制編碼為 11100101 10010110 10110101,其中的 二進制 unicode 編碼就是 01010101 10110101。

轉換為 16進制編碼格式就是 

parseint('0101010110110101',2).tostring(16) //55b5

是以它的 unicode-16 的16進制編碼是 55b5 ,試列印出來

document.write('\u55b5'); // 喵

這些函數不會對 ascii 字母和數字進行編碼,但會對一些特殊符号和多位元組字元進行16進制編碼,其中 escape 使用unicode-16 編碼,encodeuri、encodeuricomponent 使用 utf-8編碼。

他們對 單位元組 ascii 特殊符号編碼結果都是一樣的,格式是 % + 2位16進制編碼,例如空格被編碼為 %20。對于多位元組字元,escape 的編碼格式為 %u + unicode 十六進制雙位元組編碼( 如果是單位元組字元則是 % + unicode + 十六進制編碼),而 encodeuri、encodeuricomponent 則是在字元的每個 utf-8 編碼位元組前加 % ,例如 漢字 '喵' 的編碼分别是:

escape('喵'); // %u55b5

encodeuri('喵'); // %e5%96%b5

encodeuricomponent('喵'); // %e5%96%b5

encodeuri、encodeuricomponent 的差別是:

encodeuri 不對在url 上使用的特殊含義的字元編碼,如 ;/?:@&amp;=+$,# , 是以你可以使用 encodeuri 對一個url編碼後仍能正确使用它通路網絡資源或檔案目錄。

encodeuricomponent 的編碼對象是 url 上的元件,例如附加參數,他會對 ;/?:@&amp;=+$,# 這些用于分隔 uri 元件的标點符号 進行編碼,是以不應該使用它對整個 url 進行編碼。

html 字元實體可以直接用于html檔案中,寫法為 '&amp;#' +unicode 編碼的十進制數字 + ';',或者 '&amp;#x' +unicode 編碼的十六進制數字 + ';',如 大于号 &gt; 的字元實體 為 &amp;#62; 或者 &amp;#x003e; 高位可以不補0,也可以寫成 &amp;#x3e;

(62).tostring(16) //3e

document.write('&amp;#x003e;'); // 列印出 大于号 &gt;

var str = 'asd乃阿當123安迪asd123啊那的';

str.match(/[\u0000-\u00ff]/g); //單位元組字元

//["a", "s", "d", "1", "2", "3", "a", "s", "d", "1", "2", "3"]

str.match(/[\x00-\xff]/g); //單位元組字元

str.match(/[\u0100-\uffff]/g); //雙位元組字元

//["乃", "阿", "當", "安", "迪", "啊", "那", "的"]

在資料通訊中傳輸資料時,可以采用兩種資料傳輸方式:高位在前(大端,big endian)或高位在後(小端,little endian)。例如漢字'喵' 的unicode 16進制編碼為 16進制數 0x55b5 ,傳輸方式:

大端通訊:0x55 0xb5

小端通訊:0xb5 0x55

bom 是針對單個字元的本身的多位元組編碼而言的,如果字元的編碼都是使用一個位元組來編碼則不存在位元組序的問題,例如 ascii 編碼就不會存在位元組序問題。 

接收的二進制資料按每8位1位元組,每位元組使用2個十六進制字元編碼為 unicode 後,存在一個問題,如何差別是高位在前還是高位在後。

unicode規範中有一個字元的名字叫做”零寬度非換行空格“(zero width no-break space),用feff表示。這正好是兩個位元組,而且,編碼為fffe 的字元在 unicode 中不存在,是以在存儲檔案或在網絡傳輸位元組流時,可以用這個字元來标記大端通訊和小端通訊。

ucs規範建議我們在傳輸位元組流前,先傳輸字元"zero width no-break space"。

在存儲檔案時,如果一個文本檔案的頭兩個位元組是fe ff,就表示該檔案采用大端方式;如果頭兩個位元組是ff fe,就表示該檔案采用小端方式。

由于utf-8 多位元組編碼方式定義其第一個位元組的前幾位使用 1 來聲明該字元占多少個位元組,是以,utf-8編碼方式不存在編碼位元組順序的問題,但是仍然可以給 utf-8 位元組資料加上大端小端标記(帶bom 的utf-8 編碼格式)。 bom 對于 utf-8 格式檔案是沒實用意義的,是以一般推薦儲存為不帶 bom 的utf-8 格式。

這些編碼不是unicode 編碼而是區位碼和漢字内碼,這些編碼前一個位元組仍然相容ascii,gb2312(簡體) 、gbk(簡繁體)由中國大陸機構編撰,big5(繁體)由港奧台相關機構和計算機廠商編撰,區位碼在 windows 平台使用的代碼頁轉換表(codepage)映射到 unicode 編碼。

代碼頁技術現在已經廣泛為各種平台所采用。utf-7的代碼頁是65000,utf-8的代碼頁是65001。

從u+10000到u+10ffff的碼位

輔助平面(supplementary planes)中的碼位,在utf-16中被編碼為一對16比特長的碼元(即32bit,4bytes),稱作 code units called a 代理對(surrogate pair), 具體方法是:

碼位減去0x10000, 得到的值的範圍為20比特長的0..0xfffff.

高位的10比特的值(值的範圍為0..0x3ff)被加上0xd800得到第一個碼元或稱作高位代理(high surrogate), 值的範圍是0xd800..0xdbff. 由于高位代理比低位代理的值要小,是以為了避免混淆使用,unicode标準現在稱高位代理為前導代理(lead surrogates).

低位的10比特的值(值的範圍也是0..0x3ff)被加上0xdc00得到第二個碼元或稱作低位代理(low surrogate), 現在值的範圍是0xdc00..0xdfff. 由于低位代理比高位代理的值要大,是以為了避免混淆使用,unicode标準現在稱低位代理為後尾代理(trail surrogates).

lead \ trail

dc00

dc01

   …   

dfff

d800

10000

10001

103ff

d801

10400

10401

107ff

  ⋮

dbff

10fc00

10fc01

10ffff

由于高位代理、低位代理、bmp中的有效字元的碼位,三者互不重疊,搜尋是簡單的: 一個字元編碼的一部分不可能與另一個字元編碼的不同部分相重疊。這意味着utf-16是自同步(self-synchronizing): 可以通過僅檢查一個碼元就可以判定給定字元的下一個字元的起始碼元。

計算過程:

編碼 unicode 及其在 javascript 中的使用編碼 unicode 及其在 javascript 中的使用

原文釋出時間:2014-09-05

本文來自雲栖合作夥伴“linux中國”

繼續閱讀