js的发布时间早于UTF,因此在unicode字符处理上,存在很多问题.
参考
Unicode与JavaScript详解 阮一峰
字符串的扩展 阮一峰
汉字 Unicode 编码范围
es6-features nicode String & RegExp Literal
JavaScript has a Unicode problem
chinese character regExp
什么是unicode
Unicode是一种字符格式,包含世界上的所有字符.
其目的是为了解决传统字符编码的兼容问题,只要支持unicode,就能显示世界上的大部分语言.
unicode字符的表示方式
用"U+"后跟4-6位16进制数表示一个unicode字符,如中文"我"表示为 U+6211
旧版unicode3.0规定"U-"后跟8位,"U+"后跟4位
unicode的编码方式
- 从0开始,为每个字符指定一个编号,这个编号被称为"码点"
- 分"平面"(plane)存放,每个"平面"有65535个字符(2^16),目前有17个平面
- 最前面的平面被称为基本平面(BMP,Basic Multilingual Plane),剩下的平面被称为辅助平面(Supplementary Plane)
- 基本平面范围为U+0000-U+FFFF.U+D800到U+DFFF为空,用于映射辅助平面字符
- 辅助平面范围为U+010000-\u10FFFF
unicode实现方式
unicode有两类实现方式
- Unicode转换格式(UTF,Unicode Transformation Format)
- 通用字符集(UCS,Universal Coded Character Set)
UTF-8
UTF-8是一种变长格式,使用1-4个字节表示一个码点,兼容ASCII(0x0000-0x007F)
注:0x 表示其后数字为16进制数
UTF-16
UTF-16 用2或4个字节表示码点
- U+0000-U+FFFF 每字符占两节
- U+010000-\u10FFFF 每字符占四节
为了容易判断一个字符是两字/四字节字符,UTF-16通过公式将四字节的前两字节映射到U+D800-U+DBFF中,后两字节映射到U+DC00-U+DFFF中.
当读到的unicode位于U+D800-U-DBFF时,可以确定这是个4字节字符,要跟后面两个字节一起读取.
UTF-32
UTF-32 用4个字节表示码点
UCS-2
UCS-2只有基本平面(65536个字符),使用两个字节表示码点,是UTF-16的子集
JavaScript使用UCS-2编码,因此会把4字节字符当成2字节处理
1 | '我'.length //1 |
字符表示
1 '\u码点'
1 | console.log('我') // 我 |
该方法不支持辅助平面字符
'\u20000' // " 0"
2 '\u高字节\u低字节'
'\ud840\udc00'//"𠀀"
'\ud840\udc00' === "𠀀" //true
3 '\u{码点}'
'\u{20000}' // "𠀀"
'\ud840\udc00' === '\u{20000}' //true
字符处理
获取字符码点
string.codePointAt(index)
返回unicode码点
"𠀀".codePointAt(0) // 131072
131072 === 0x20000
"𠀀".codePointAt(0).toString(16) // "20000"
获取字符长度
Array.from(string).length
Array.from("𠀀").length // 1
反转字符串
Array.from(string).reverse().join("")
Array.from("𠀀𠀁𠀂𠀃").reverse().join('') // "𠀃𠀂𠀁𠀀"
直接用数组反转会出错
'𠀀𠀁𠀂𠀃'.split('').reverse().join('') // "�𠀂𠀁𠀀�"
字符遍历
1 for (let i of string) {}
var string = "𠀀𠀁𠀂𠀃"
for (let i of string) {
console.log(i) //𠀀 𠀁 𠀂 𠀃
}
2 检测字符范围是否在0xD800-0xDBFF间,是的话拼接字符
var output = [];
var index = -1;
var string = "𠀀𠀁𠀂𠀃";
while (++index <string.length) {
var code = string.charCodeAt(index);
if (code >= 0xD800 && code <= 0xDBFF ) {
s = string[index] + string[++index];
output.push(s);
} else {
output.push(string[index])
}
}
for (var i in output) {
console.log(output[i])
}
//𠀀 𠀁 𠀂 𠀃
作为对比,另外两种遍历方式无法正确处理辅助平面字符
for (var i in string) {
console.log(string[i]) //��������
}
for (var i=0; i < string.length; i++) {
console.log(string[i]) //��������
}
正则处理
可用u修饰符判断4字节字符
var regExp = /^.$/u
regExp.test('𠀀') // true
//无法识别4字节字符
var regExp1 = /^.$/
regExp1.test('𠀀') // false
中文字符判断
/[\u4E00-\u9FCC\u3400-\u4DB5\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\ud840-\ud868][\udc00-\udfff]|\ud869[\udc00-\uded6\udf00-\udfff]|[\ud86a-\ud86c][\udc00-\udfff]|\ud86d[\udc00-\udf34\udf40-\udfff]|\ud86e[\udc00-\udc1d]/
var regExp = /[\u4E00-\u9FCC\u3400-\u4DB5\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\ud840-\ud868][\udc00-\udfff]|\ud869[\udc00-\uded6\udf00-\udfff]|[\ud86a-\ud86c][\udc00-\udfff]|\ud86d[\udc00-\udf34\udf40-\udfff]|\ud86e[\udc00-\udc1d]/;
regExp.test('𠀀'); // true
其他
number.toString(radix) 转换数字为指定进制的字符串
var num = 1000;
num.toString(16) // "3e8"
1000 === 0x3e8 // true
string.charCodeAt(index) 返回字符码点,会把4节字符当成两个2字节字符处理
'我'.charCodeAt(0).toString(16) // "6211"
'我'.charCodeAt(1).toString(16) // "NaN"
'\u6211' //"我"
'𠀀'.charCodeAt(0).toString(16) // "d840"
'𠀀'.charCodeAt(1).toString(16) // "dc00"
'\ud840' // "�"
'\ud840\udc00' // "𠀀"