ES6类型扩展-Unicode扩展
JS中的字符串是⼀组由引号包裹的16位Unicode字符组成的字符序列。在Unicode引⼊扩展字符集之后,JS中的Unicode编码规则也进⾏了变更,本⽂介绍ES6中关于Unicode的相关扩展。
基本概念
Unicode的⽬标是为世界上每⼀个字符提供唯⼀标识符。唯⼀标识符(code point)也叫作码位或码点,码位⼜称标识符的字符编码。
ES6之前,JS的字符串以16位字符编码(UTF-16)为基础,每个16位字符序列(2个字节)是⼀个编码单元(code unit),简称码元。字符串所有的属性和⽅法都是基于16位字符序列。
BMP
最常⽤的Unicode字符使⽤16位编码序列字符,属于“基本多语种平⾯”(Basic Multilingual Plane BMP),⼜称“零断⾯”(plan 0)。BMP是Unicode中的⼀个编码区段,编码介于U+0000到U+FFFF之间,超过这个范围的码位则属于某个辅助平⾯(supplementary plane),⼜称扩展平⾯,这些码位⽤16位字符已⽆法表⽰。
为了解决辅助平⾯码位的表⽰问题,UTF-16引⼊了代理对(surrogate pairs),规定⽤两个16位编码来表⽰⼀个码位,这意味着字符串中字符有两种:⼀种是⽤⼀个码元来表⽰的BMP字符(16位);另⼀种是⽤两个码元表⽰的辅助平⾯字符(32位)
表⽰⽅法
JS允许采⽤\uxxxx形式表⽰⼀个字符,xxxx表⽰字符的Unicode码位。这种表⽰法只限于码位在\u0000 ~ \uFFFF之间的字符,超出该范围必须⽤两个双字节形式表⽰。
console.log('\u0061') // a
console.log('\uD842\uDFB7') //
console.log('\u20BB7') //  7
\u20BB7由于超出了表⽰范围,js会理解成'\u20BB'+'7',所以最终显⽰⼀个“ 7“
ES6对这点做出了改进,只要把码位放到⼤括号⾥就能正确读取该字符。
console.log('\u{20BB7}') //
console.log('\u{20BB7}' === '\uD842\uDFB7') // true
console.log('\u{0061}') // a
现在同⼀个字符,有下⾯⼏种表⽰⽅法:
console.log('\a' === 'a') // true
console.log('\x61' === 'a') // true
console.log('\u0061' === 'a') // true
console.log('\u{61}' === 'a') // true
字符编解码
codePointAt()
ES6新增了codePointAt()⽅法,该⽅法接收编码单元的位置作为参数(⾮字符串位置),返回字符串中给定位置对应的Unicode码位。
注意: charCodeAt()⽅法接收字符串位置作为参数,返回字符串中给定位置对应的Unicode码位。
var text = " a" ;
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.dePointAt(0)); // 134071
console.dePointAt(1)); // 57271
console.dePointAt(2)); // 97
对于BMP字符,codePointAt()⽅法的返回值与 charCodeAt() 相同,如字符“a”,都返回97。
对于辅助平⾯的32位字符,如“ ”,charCodeAt()和codePointAt()⽅法都分为两部分返回。charCodeAt(0)和chatCodeAt(1)分别返回前16位和后16位的编码;⽽codePointAt(0)和codePointAt(1)分别返回32位编码和后16位的编码
可以使⽤codePointAt() ⽅法判断⼀个字符是否位于BMP。
function is32Bit(c) {
dePointAt(0) > 0xFFFF;
}
console.log(is32Bit(" " )); // true
console.log(is32Bit("a")); // false
String.fromCodePoint()
String.fromCodePoint()⽅法是codePointAt()的反向操作,该⽅法返回给定码位所对应的字符。
注意: String.fromCharCode()⽅法也⽤于返回给定码位所对应的字符,但是它不能识别32位的UTF-16字符。
console.log(String.fromCodePoint(0x20bb7)); //
console.log(String.fromCodePoint(0x61)); // a
console.log(String.fromCharCode(0x20bb7)); //
console.log(String.fromCharCode(0x61)); // a
String.fromCodePoint()⽅法同样可以接收多个参数,返回合并后的字符串
String.fromCodePoint(104,101,108,108,111) // hello
normalize()
许多欧洲语⾔有语调符号和重⾳符号,例如Ǒ。Unicode提供了两种⽅式表⽰这些特殊符号。⼀种是直接提供带重⾳符号的字符,⽐
如Ǒ('\u01D1')。另⼀种是通过合成原字符和重⾳符,⽐如o和ˇ('\u004F\u030C')
这两种表⽰⽅式在视觉上是相同的,但是JS⽆法识别
console.log('\u01D1' === '\u004F\u030C')
console.log('\u01D1'.length) // 1
console.log('\u004F\u030C'.length); // 2
normalize()⽅法可以把字符的不同表⽰⽅式统⼀成相同的形式,这样JS就能正确识别了,这称为Unicode正规化。
console.log('\u01D1'=== '\u004F\u030C'.normalize()); //true
normalize()⽅法可以接受⼀个参数来指定normalize的⽅式,参数的四个可选值如下:
1、NFC,默认参数,表⽰“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价
2、NFD,表⽰“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符
3、NFKC,表⽰“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,⽐如“囍”和“喜喜”。(这只是⽤来举例,normalize⽅法不能识别中⽂。)
4、NFKD,表⽰“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符
console.log('\u01D1' === '\u004F\u030C'.normalize('NFC')); //true
console.log('\u004F\u030C' === '\u01D1'.normalize('NFD')); //true
console.log('\u01D1' === '\u004F\u030C'.normalize('NFKC')); //true
console.log('\u004F\u030C' === '\u01D1'.normalize('NFKD')); //true
在开发国际化应⽤时normalize()⽅法很有⽤,但它不能识别三个或三个以上字符的合成。这时候只能通过正则表达式,对Unicode编号区间进⾏判断。
U修饰符
ES6之前的正则表达式不能正确处理32位编码的字符,ES6为正则表达式添加了u修饰符,含义为“Unicode模式”,让它可以正确处理⼤于\uFFFF的 Unicode 字符,即32位编码的字符。
console.log(/^\uD842/.test('\uD842\uDFB7')) // true
console.log(/^\st('\uD842\uDFB7')) // false
设置u修饰符后,位于辅助平⾯的32位字符会被识别为1个字符。
元字符.
元字符.,在正则表达式中含义是匹配除了换⾏符外的任意单个字符。对于码位⼤于0xFFFF的Unicode字符,必须加上u修饰符才能正常识别。
let s = ' 'js在字符串中添加字符
console.log(s.length); // 2
console.log(/^.$/.test(s));//false
console.log(/^.$/u.test(s)); //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}/u.test('aa') // true
/ {2}/u.test('  ') // true
/a{2}/.test('aa') // true
/ {2}/.test('  ') // false
预定义模式
\S表⽰预定义模式,匹配所有⾮空格的字符。只有加了u修饰符才能正确匹配码点⼤于0xFFFF的 Unicode 字符
/^\S$/.test(' ') // false
/^\S$/u.test(' ') // true
字符串长度
ES6不⽀持字符串码位数量的检测,所以length属性仍然返回字符串编码单元的数量。但是可以利⽤[\s\S]结合u修饰符,写出⼀个正确返回字符串长度的函数。
function codePointLength(text) {
var result = text.match(/[\s\S]/gu);
return result ? result.length : 0;
}
var s = '  ';
console.log(codePointLength(s)); // 2
console.log(s.length); // 4