ixlei / jsnotes

1 stars 1 forks source link

javaScript中的Unicode #6

Open ixlei opened 6 years ago

ixlei commented 6 years ago

javaScript中的Unicode

踩坑 在做某个业务的时候,后端童鞋给到的某个字段的值大概都长这样 javascript "\n#\u0008\u0005\u0012\u001f\u0012\u001d\u0005星球大战\u0006:原力觉醒\n\u000f\u0008\u0006\u0012\u000b\u0012\t张学友\n\n\u0008\u0003\u0012\u0006\u0012\u0004Dior\n\u0012\u0008\u0007\u0012\u000e\u0012\u000c英雄联盟" 对于这样的数据,我是拒绝的。对于这些 unicode,怎么解析呢?它到底是什么?

什么是编码

其实编码原理很简单,在计算中并非直接存储字符(英文、中文等),而是存储这些字符对应的一个数字,这些字符在网络传输的时候,也是传输字符对应的数字,编码就是将字符转化成相应的数字。

ASCII

大学C语言入门课程最先学习的知识,它用了一个字节的7位表示一个字符,比如字符A的ASCII码是65。ASCII码只用了7个bit编码,注定能编码的字符很少,只有128个。

Unicode编码

ASCII编码只能编码128个字符,而中国汉子就有6000 +,这样显然不够的,然后中国推出GBK 编码,用了两个字节来编码。中国编码用了GBK,那其他国家呢?于是为了统一,推出了Unicode编码。在1991年推出了UCS统一编码,实际应用的是USC-2,用了2个字节来编码,能编码65536个字符。JavsScript中的编码就是用的这个编码方式。

编码都是不涉及计算机存储,传输的。但是在遇到两个字节编码的字符的时候,有个系统是大端顺序读取,Windows就是这样,而Mac上是按照小端顺序读取。如果没有个转化格式,那么就乱了,此时UTF(Unicode Transformation Format,简称为UTF)就产生了。因此,UTF-16使用了大端序(Big-Endian,简写为UTF-16 BE)、小端序(Little-Endian,简写为UTF-16 LE)以及BOM(byte order mark)的概念。避免上述情况的出现。

JavaScript中的编码

JavaScript刚创建出来的时候,只有USC-2编码方式可选,所以就一只用了这个编码方式。上文也提到USC-2一开始使用了两个字节来编码,能够编码65536个字符,从0x0 - 0xFFFF,这个也被称为基本平面(BMP-—Basic Multilingual Plane)。但是后来发现这样的Unicode编码也是不够用,扩展了其他补充平面,从0x010000 - 0x10FFFF,共16个。对于补充平面的字符。对于UTF-16编码方式来说,在0x0 - 0xFFFF之间的编码方式直接使用了2个字节,而补充平面使用了4个字节来编码,其中前两个字节范围是0xD800 - 0xDBFF,后两个字节范围是0xDC00 - 0xDFFF。通过下面的方式完成映射:

H = Math.floor((c-0x10000) / 0x400)+0xD800 
L = (c – 0x10000) % 0x400 + 0xDC00

USC-2编码的那些坑

var text = "𠮷"
console.log(text.length) // 2
console.log(/^.$/.test(text)) // false
console.log(text.split('').reverse()) // ["�", "�"]
console.log(String.fromCharCode(text.codePointAt(0))) // "�"

(1)判断长度失效

对于ES5来说,可以将此类转化以下

var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; // 匹配UTF-16的代理对

function countSymbols(string) {
    return string
        .replace(regexAstralSymbols, '_')
        .length;
}
countSymbols("𠮷"); // 1

对于ES6来说

function codePointLength(text) {
    var result = text.match(/[\s\S]/gu);
    return result ? result.length : 0;
}
codePointLength("𠮷") // 1

(2)正则表达式

console.log(/^.$/u.test(text)); // true

字符反转

function reverse(string) {
    return Array.from(string).reverse().join('');
}
reverse("𠮷") // "𠮷"

String.fromCharCode

var text = "𠮷";
console.log(text.codePointAt(0)) // 0x20bb7 (unicode)
var hightByte = text.charCodeAt(0) // 0xd842
var lowByte = text.charCode(1) // 0xdfb7
console.log(String.fromCharCode(hightByte, lowByte)) // "𠮷"
console.log(String.fromCodePoint(text.codePointAt(0))) // "𠮷"

写在最后

最后发现这样的编码无法编码成想要的字符,最后让后端童鞋编码成base64编码,然后解码。