前面的話

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

 

概述

  Unicode的目標(biāo)是為世界上每一個字符提供唯一標(biāo)識符,唯一標(biāo)識符稱為碼位或碼點(code point)。而這些碼位是用于表示字符的,又稱為字符編碼(character encode) 

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

【BMP】

  最常用的Unicode字符使用16位序列編碼字符,屬于“基本多語種平面”(Basic Multilingual Plane BMP),也稱為“零斷面”(plan 0), 是Unicode中的一個編碼區(qū)段,編碼介于U+0000——U+FFFF之間。超過這個范圍的碼位則要歸屬于某個輔助平面或稱為擴(kuò)展平面(supplementary plane),其中的碼位僅用16位就無法表示了

  為此,UTF-16引入了代理對(surrogate pairs),規(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 對這一點做出了改進(jìn),只要將碼位放入大括號,就能正確解讀該字符

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

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

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

  上面代碼中,最后一個例子表明,大括號表示法與四字節(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ù)值

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

 text = "??a"0)); console.log(text.charCodeAt(1)); console.log(text.charCodeAt(2)); 0)); console.log(text.codePointAt(1)); console.log(text.codePointAt(2));

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

  對于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) {    return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("??" )); // true console.log(is32Bit("a")); // false

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

【String.fromCodePoint()】

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

  ECMAScript通常會提供正反兩種方法??梢允褂胏odePointAt() 來提取字符串內(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ù),則它們會被合并成一個字符串返回

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

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

 

for...of

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

 

normalize()

  許多歐洲語言有語調(diào)符號和重音符號。為了表示它們,Unicode提供了兩種方法。一種是直接提供帶重音符號的字符,比如ǒ(\u01D1)。另一種是提供合成符號(combining character),即原字符與重音符號的合成,兩個字符合成一個字符,比如O(\u004F)和ˇ(\u030C)合成ǒ(\u004F\u030C)

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

console.log('\u01D1'==='\u004F\u030C'); //falseconsole.log('\u01D1'.length); // 1console.log('\u004F\u030C'.length); // 2

  上面代碼表示,JavaScript將合成字符視為兩個字符,導(dǎo)致兩種表示方法不相等。

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

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

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

  1、NFC,默認(rèn)參數(shù),表示“標(biāo)準(zhǔn)等價合成”(Normalization Form Canonical Composition),返回多個簡單字符的合成字符。所謂“標(biāo)準(zhǔn)等價”指的是視覺和語義上的等價

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

  2、NFD,表示“標(biāo)準(zhǔn)等價分解”(Normalization Form Canonical Decomposition),即在標(biāo)準(zhǔn)等價的前提下,返回合成字符分解的多個簡單字符

console.log('\u004F\u030C'==='\u01D1'.normalize("NFD")); //trueconsole.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()方法目前不能識別三個或三個以上字符的合成。這種情況下,還是只能使用正則表達(dá)式,通過Unicode編號區(qū)間判斷

 

U修飾符

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

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

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

【點號】

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

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

【大括號】

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

/\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ù)

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

console.log(s.length); // 4console.log(codePointLength(s)); // 2

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

【檢測支持】

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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

http://www.cnblogs.com/xiaohuochai/p/7230328.html