ES6(四):關(guān)于Unicode的相關(guān)擴展

前面的話


JS中的字符串類型是由引號括起來的一組由16位Unicode字符組成的字符序列啥纸。在過去珠洗,16位足以包含任何字符棒卷,直到Unicode引入了擴展字符集,編碼規(guī)則不得不進行變更咒锻。本文將詳細介紹ES6關(guān)于Unicode的相關(guān)擴展

概述

Unicode的目標是為世界上每一個字符提供唯一標識符,唯一標識符稱為碼位或碼點(codepoint)守屉。而這些碼位是用于表示字符的惑艇,又稱為字符編碼(characterencode)

ES6之前,JS 的字符串以16 位字符編碼(UTF-16)為基礎(chǔ)胸梆。每個16 位序列(相當于2個字節(jié))是一個編碼單元(codeunit)敦捧,可簡稱為碼元,用于表示一個字符碰镜。字符串所有的屬性與方法(如length屬性與charAt()方法等)都是基于16位序列

【BMP】

最常用的Unicode字符使用16位序列編碼字符,屬于“基本多語種平面”(Basic Multilingual Plane BMP)习瑰,也稱為“零斷面”(plan0)绪颖, 是Unicode中的一個編碼區(qū)段,編碼介于U+0000——U+FFFF之間甜奄。超過這個范圍的碼位則要歸屬于某個輔助平面或稱為擴展平面(supplementaryplane)柠横,其中的碼位僅用16位就無法表示了
為此,UTF-16引入了代理對(surrogatepairs)课兄,規(guī)定用兩個16位編碼來表示一個碼位牍氛。

字符串里的字符有兩種:

  • 一種由一個碼元(共16 位)來表示BMP字符,
  • 另一種用兩個碼元(共32 位)來表示輔助平面字符

大括號表示

JavaScript 允許采用\uxxxx形式表示一個字符烟阐,其中xxxx表示字符的Unicode碼位

// "a"
console.log("\u0061");

但是搬俊,這種表示法只限于碼位在\u0000~\uFFFF之間的字符。超出這個范圍的字符蜒茄,必須用兩個雙字節(jié)的形式表示

// "??"
console.log("\uD842\uDFB7");
// "?7"
console.log("\u20BB7");
  • 上面代碼表示唉擂,如果直接在\u后面跟上超過0xFFFF的數(shù)值(比如\u20BB7),JavaScript會理解成\u20BB+7檀葛。所以會顯示一個特殊字符玩祟,后面跟著一個7

ES6對這一點做出了改進,只要將碼位放入大括號屿聋,就能正確解讀該字符

// "??"
console.log("\u{20BB7}");

// "ABC"
console.log("\u{41}\u{42}\u{43}");

let hello = 123;
// 123
console.log(hell\u{6F});

// true
console.log('\u{1F680}' === '\uD83D\uDE80');

上面代碼中空扎,最后一個例子表明,大括號表示法與四字節(jié)的UTF-16 編碼是等價的润讥。
有了這種表示法之后转锈,JavaScript共有6種方法可以表示一個字符

'\z' === 'z'// true
'\172' === 'z'// true
'\x7A' === 'z'// true
'\u007A' === 'z'// true
'\u{7A}' === 'z'// true

字符編解碼

【codePointAt()】

ES6新增了完全支持UTF-16的方法codePointAt(),該方法接受編碼單元的位置而非字符位置作為參數(shù)象对,返回與字符串中給定位置對應(yīng)的碼位黑忱,即一個整數(shù)值

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
  • 對于BMP字符,codePointAt()方法的返回值與charCodeAt() 相同,如'a'甫煞,都返回97
  • 對于輔助平面的32位字符菇曲,如'??',charCodeAt()和codePointAt()方法都分為兩部分返回
  • charCodeAt(0)和chatCodeAt(1)分別返回前16位和后16位的編碼抚吠;而codePointAt(0)和codePointAt(1)分別返回32位編碼及后16位的編碼
  • 判斷一個字符是否是BMP常潮,對該字符調(diào)用codePointAt() 方法就是最簡單的方法
function is32Bit(c) {    
  returnc.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("??" )); // true 
console.log(is32Bit("a")); // false

16位字符的上邊界用十六進制表示就是FFFF,因此任何大于該數(shù)字的碼位必須用兩個碼元(共32位)表示

【String.fromCodePoint()】

ES5提供的String.fromCharCode方法楷力,用于從碼位返回對應(yīng)字符喊式,但是這個方法不能識別32位的UTF-16字符

ECMAScript通常會提供正反兩種方法∠舫可以使用codePointAt()來提取字符串內(nèi)中某個字符的碼位岔留,也可以借助String.fromCodePoint()根據(jù)給定的碼位來生成一個字符

console.log(String.fromCharCode(0x20bb7));//"?"
console.log(String.fromCodePoint(0x20bb7)); // "??"
console.log(String.fromCharCode(0x0bb7));// "?"
  • 上面代碼中,String.fromCharCode不能識別大于0xFFFF的碼位检柬,所以0x20BB7就發(fā)生了溢出献联,最高位2被舍棄了,最后返回碼位U+0BB7對應(yīng)的字符何址,而不是碼位U+20BB7對應(yīng)的字符

如果String.fromCodePoint()方法有多個參數(shù)里逆,則它們會被合并成一個字符串返回

// true
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' 

可以將String.fromCodePoint() 視為 String.fromCharCode() 的完善版本。兩者處理BMP 字符時會返回相同結(jié)果用爪,只有處理BMP 范圍之外的字符時才會有差異

for...of

對于32位的輔助平面字符來說原押,使用forforin循環(huán),可能得不到正確的結(jié)果

var s = '??a';
for(let chins) {
  console.log(s[ch]);
} //?//?//a

而for...of循環(huán)可以正確的識別32位的UTF-16字符

var s = '??a';
for(let ch of s) {
  console.log(ch);
}//??//a

normalize()

許多歐洲語言有語調(diào)符號和重音符號偎血。為了表示它們诸衔,Unicode提供了兩種方法。一種是直接提供帶重音符號的字符烁巫,比如ǒ(\u01D1)署隘。另一種是提供合成符號(combiningcharacter),即原字符與重音符號的合成亚隙,兩個字符合成一個字符磁餐,比如O(\u004F)ˇ(\u030C)合成ǒ(\u004F\u030C)

這兩種表示方法,在視覺和語義上都等價阿弃,但是JavaScript不能識別

console.log('\u01D1'==='\u004F\u030C');//false
console.log('\u01D1'.length);// 1
console.log('\u004F\u030C'.length);// 2
  • 上面代碼表示诊霹,JavaScript將合成字符視為兩個字符,導(dǎo)致兩種表示方法不相等渣淳。

ES6提供字符串實例的normalize()方法脾还,用來將字符的不同表示方法統(tǒng)一為同樣的形式,這稱為Unicode正規(guī)化

console.log('\u01D1'==='\u01D1'.normalize());//true
console.log('\u01D1'=== '\u004F\u030C'.normalize());//true

normalize方法可以接受一個參數(shù)來指定normalize的方式入愧,參數(shù)的四個可選值如下

  • 1. NFC鄙漏,默認參數(shù)嗤谚,表示“標準等價合成”(Normalization Form Canonical Composition),返回多個簡單字符的合成字符怔蚌。所謂“標準等價”指的是視覺和語義上的等價
console.log('\u01D1'==='\u01D1'.normalize("NFC"));//true
console.log('\u01D1'=== '\u004F\u030C'.normalize("NFC"));//true
  • 2. NFD巩步,表示“標準等價分解”(Normalization Form Canonical Decomposition),即在標準等價的前提下桦踊,返回合成字符分解的多個簡單字符
console.log('\u004F\u030C'==='\u01D1'.normalize("NFD"));//true
console.log('\u004F\u030C'=== '\u004F\u030C'.normalize("NFD"));//true
  • 3. NFKC椅野,表示“兼容等價合成”(Normalization Form Compatibility Composition),返回合成字符籍胯。所謂“兼容等價”指的是語義上存在等價竟闪,但視覺上不等價,比如“囍”和“喜喜”杖狼。(這只是用來舉例炼蛤,normalize方法不能識別中文。)

  • 4. NFKD蝶涩,表示“兼容等價分解”(Normalization Form Compatibility Decomposition)鲸湃,即在兼容等價的前提下,返回合成字符分解的多個簡單字符

在開發(fā)國際化應(yīng)用時子寓,normalize() 方法非常有用。但normalize()方法目前不能識別三個或三個以上字符的合成笋除。這種情況下斜友,還是只能使用正則表達式,通過Unicode編號區(qū)間判斷

U修飾符

正則表達式可以完成簡單的字符串操作垃它,但默認將字符串中的每一個字符按照16位編碼處理鲜屏。為了解決這個問題, ES6對正則表達式添加了u修飾符国拇,含義為“Unicode模式”洛史,用來正確處理大于\uFFFF的Unicode 字符。也就是說酱吝,會正確處理四個字節(jié)的UTF-16 編碼

/^\uD83D/u.test('\uD83D\uDC2A')// false
/^\uD83D/.test('\uD83D\uDC2A')// true

一旦為正則表達式設(shè)置了u 修飾符也殖,正則表達式將會識別32位的輔助平面字符為1個字符,而不是兩個

【點號】

點(.)字符在正則表達式中务热,含義是除了換行符以外的任意單個字符忆嗜。對于碼位大于0xFFFF的Unicode 字符,點字符不能識別崎岂,必須加上u修飾符

var text = "??";
console.log(text.length); // 2
console.log(/^.$/.test(text));//false
console.log(/^.$/u.test(text));//true

【大括號】

ES6 新增了使用大括號表示Unicode 字符捆毫,這種表示法在正則表達式中必須加上u修飾符,才能識別當中的大括號冲甘,否則會被解讀為量詞

/\u{61}/.test('a')// false
/\u{61}/u.test('a')// true
/\u{20BB7}/u.test('??')// true

【量詞】

使用u修飾符后绩卤,所有量詞都會正確識別碼點大于0xFFFF的Unicode 字符

/a{2}/.test('aa')// true
/a{2}/u.test('aa')// true
/??{2}/.test('????')// false
/??{2}/u.test('????')// true

【預(yù)定義模式】

u修飾符也影響到預(yù)定義模式途样,能否正確識別碼點大于0xFFFF的Unicode 字符

/^\S$/.test('??') // false
/^\S$/u.test('??')// true

【字符串長度】

上面代碼的\S是預(yù)定義模式,匹配所有不是空格的字符濒憋。只有加了u修飾符何暇,它才能正確匹配碼點大于0xFFFF的Unicode 字符

雖然ES6不支持字符串碼位數(shù)量的檢測,length屬性仍然返回字符串編碼單元的數(shù)量跋炕。利用[\s\S]赖晶,再加上u修飾符,就可以寫出一個正確返回字符串長度的函數(shù)

function codePointLength(text) {  
  var result = text.match(/[\s\S]/gu);  
  returnresult ? result.length : 0;
}
var s = '????';
console.log(s.length); // 4
console.log(codePointLength(s));// 2

【檢測支持】

u修飾符是語法層面的變更辐烂,嘗試在不兼容ES6JS 引擎中使用它會拋出語法錯誤遏插。如果要檢測當前引擎是否支持u修飾符,最安全的方式是通過以下函數(shù)來判斷

function hasRegExpU() {    
  try {        
    var pattern =newRegExp(".", "u");   
    return true;
  } catch (ex) {        
    return false;
  }
}

這個函數(shù)使用了RegExp構(gòu)造函數(shù)并傳入字符串'u'作為參數(shù)纠修,該語法即使在舊版JS 引擎中也是有效的胳嘲。但是,如果當前引擎不支持u修飾符則會拋出錯誤

其他章節(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扣草,一起剝皮案震驚了整個濱河市了牛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辰妙,老刑警劉巖鹰祸,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異密浑,居然都是意外死亡蛙婴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門尔破,熙熙樓的掌柜王于貴愁眉苦臉地迎上來街图,“玉大人,你說我怎么就攤上這事懒构〔图茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵胆剧,是天一觀的道長絮姆。 經(jīng)常有香客問我,道長赞赖,這世上最難降的妖魔是什么滚朵? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮前域,結(jié)果婚禮上辕近,老公的妹妹穿的比我還像新娘。我一直安慰自己匿垄,他們只是感情好移宅,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布归粉。 她就那樣靜靜地躺著,像睡著了一般漏峰。 火紅的嫁衣襯著肌膚如雪糠悼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天浅乔,我揣著相機與錄音倔喂,去河邊找鬼。 笑死靖苇,一個胖子當著我的面吹牛席噩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贤壁,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悼枢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脾拆?” 一聲冷哼從身側(cè)響起馒索,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎名船,沒想到半個月后绰上,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡渠驼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年渔期,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渴邦。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拘哨,靈堂內(nèi)的尸體忽然破棺而出谋梭,到底是詐尸還是另有隱情,我是刑警寧澤倦青,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布瓮床,位于F島的核電站,受9級特大地震影響产镐,放射性物質(zhì)發(fā)生泄漏隘庄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一癣亚、第九天 我趴在偏房一處隱蔽的房頂上張望丑掺。 院中可真熱鬧,春花似錦述雾、人聲如沸街州。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唆缴。三九已至鳍征,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間面徽,已是汗流浹背艳丛。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趟紊,地道東北人氮双。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像织阳,于是被迫代替她去往敵國和親眶蕉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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