前面的话
在ES6之前,js的字符串以16位字符编码为基础,16位即两个字节表示一个字符。采用\uxxxx形式表示一个字符,其中xxxx 表示字符的Unicode码点。本文将介绍ES6 关于Unicode的扩展。
●大括号表示法
ES6之前的写法:
{
// a
console.log('\u0061');
}
但这种表示方法只限于码点在\u0000 ~ \uFFFF之间的字符。超出这个范围的字符,必须用 2个双字节的形式表示。
{
console.log("\uD842\uDFB7");// ?
console.log('\u20BB7');// ₻7
}
上面代码表示,如果直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),js会理解成\u20BB + 7,由于\u20BB是一个不可打印的字符,所以会显示一个特殊字符,后面跟一个7。
ES6的改进:
只要将码点放入大括号,就能正确解读该字符。
{
console.log('\u{20BB7}');// ?
console.log('\u{41}\u{42}\u{43}');// ABC
let hello = 123;
console.log('hell\u{6F}');// hello
console.log('\u{1F680}'=== '\uD83D\uDE80');// true
}
上面最后一个这个例子表明:大括号表示法与四字节的UTF-16编码是等价的
有了这种表示法,js共有6种方法表示一个字符:
{
console.log('\z' === 'z');
console.log('\172' === 'z');
console.log('\x7A' === 'z');
console.log('\u007A' === 'z');
console.log('\u{7A}' === 'z');
}
●codePointAt()
在js内部,每个字符固定为2个字节。对于那些需要用4个字节存储的字符(Unicode码点大于0xFFFF的字符),js会认为他们是2个字符。
es6之前对于这种码点大于0xFFFF的字符,js无法正确处理,误判为字符串长度为2。chatAt()无法读取整个字符,charCodeAt()只能返回前2个字节和后2个字节的值(且为10进制)
{
var s = '?';
console.log(s.length);// 2
console.log(s.charAt(0));// �
console.log(s.charAt(1));// �
console.log(s.charCodeAt(0));// 55362
console.log(s.charCodeAt(1));// 57271
}
ES6提供的codePointAt()方法,能够正确处理4个字节存储的字符,返回一个字符的码点。
{
var s = '?a';
console.log(s.codePointAt(0));// 134071
console.log(s.codePointAt(1));// 57271
console.log(s.codePointAt(2));// 97
}
上面这个例子:js将’?a’视为3个字符,codePointAt()在第一个字符上正确的识别了‘?’,返回了它的十进制码点134071。在第二个字符(即‘?’的后面两个字节)和第三个字符‘a’上,codePointAt()的结果与charCodeAt()相同。
● String.fromCodePoint()
ES6提供的String.fromCodePoint()与codePointAt()正好相反,它是根据码点返回对应的字符.
ES6之前的String.fromCharCode()方法不能识别占4个字节的字符。
{
console.log( String.fromCharCode(0x20BB7));// ஷ
}
ES6提供的String.fromCodePoint(),可以识别大于0xFFFF的字符,弥补了String.fromCharCode()的不足
{
console.log(String.fromCodePoint(0x20BB7));// ?
console.log(String.fromCodePoint(0x78,0x1f680,0x79));// x?y
console.log(String.fromCodePoint(0x78,0x1f680,0x79) === 'x\uD83D\uDE80y');// true
}
注意: fromCodePoint方法定义在String对象上,而CodePointAt方法定义在字符串实例上
● for …of
对于32位的UTF-16字符,使用for或则for…in可能得不到正确的结果
{
let s = '?a';
for(let ch in s){
console.log(s[ch]);
}
// �
// �
// a
}
利用for … of可以得到正确的结果
{
let s = String.fromCodePoint(0x20BB7,0x61);
for(let ch of s){
console.log(ch);
}
// ?
// a
}
● normalize()
许多欧洲语言有语调符号和重音符号,为了表示他们,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号,即原字符与重音符号合成一个字符,比如o(\u004F)和(\u030C)合成Ǒ(\u004F\u030C)
这两种表示方法在视觉上和语义上都是等价的,但是js无法识别,js将合成的字符视为两个字符,导致两种表示方法不等价。
{
console.log('\u01D1' === '\u004F\u030C');// false
console.log('\u01D1'.length);// 1
console.log('\u004F\u030C'.length);// 2
}
ES6 为提供了normalize方法,用来将字符的不同表示方法统一为同样的形式,着称为Unicode正规化
{
console.log('\u01D1'.normalize() === '\u004F\u030C'.normalize());// true
}
normalize()方法可以接受一个参数来指定normalize的方式,参数的4个可选值:
1: NFC ,默认参数,表示“标准等价合成”,返回多个简单的字符合成字符。所“标准等价”指的是视觉和语义上的等价
2:NFD,表示“标准等价分解”,即在标准等价的前提下,返回合成字符分解出的多个简单字符
3:NFKC,表示“兼容等价合成”,返回合成字符,所谓“兼容等价”指的是语义上的等价,视觉上不等价,比如“囍”和“喜喜”。(这里 只是举例,normalize()并不能识别中文)
4:NFKD,表示“兼容等价分解”,即在兼容等价的前提下,返回合成字符分解出的多个简单字符。
console.log('\u004F\u030C'.normalize('NFC').length);// 1
console.log('\u004F\u030C'.normalize('NFD').length);// 2
上面这个例子表示,NFC参数返回字符的合成形式,NFD参数返回字符的分解形式。
不过normalize()方法不能识别3个或3个以上的字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间来判断。