1. 参考
  2. 什么是unicode
    1. unicode字符的表示方式
    2. unicode的编码方式
  3. unicode实现方式
    1. UTF-8
    2. UTF-16
    3. UTF-32
    4. UCS-2
  4. 字符表示
  5. 字符处理
    1. 获取字符码点
    2. 获取字符长度
    3. 反转字符串
    4. 字符遍历
    5. 正则处理
    6. 其他

unicode字符处理

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
2
3
4
5
6
7
8
9
10
'我'.length //1
'𠀀'.length //2

'𠀀'.charCodeAt(0).toString(16) // "d840"
'𠀀'.charCodeAt(1).toString(16) // "dc00"
'\ud840\udc00' //"𠀀"

'我'.codePointAt(0).toString(16) "6211"
'𠀀'.codePointAt(0).toString(16) "20000"
'\u{20000}' //'𠀀'

字符表示

1 '\u码点'

1
2
console.log('我') // 我
console.log('\u6211') // 我

该方法不支持辅助平面字符

'\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' // "𠀀"