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.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位的编码

可以使用codePointAt() 方法判断一个字符是否位于BMP。

function is32Bit(c) {
    return c.codePointAt(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(/^\uD842/u.test('\uD842\uDFB7')) // false

设置u修饰符后,位于辅助平面的32位字符会被识别为1个字符。

元字符.

元字符.,在正则表达式中含义是匹配除了换行符外的任意单个字符。对于码位大于0xFFFF的Unicode字符,必须加上u修饰符才能正常识别。

let s = '𠮷'
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
posted @ 2021-09-29 11:35  wmui  阅读(163)  评论(0编辑  收藏  举报