kaola-fed / blog

kaola blog
723 stars 56 forks source link

从两个字符看 JavaScript 编码 #244

Open lllang opened 6 years ago

lllang commented 6 years ago

引子

  1. 前几天在shopms中有一个页面报错,看了一下发现是在最外面拿到后端给的同步数据的时候JSON.parse()的时候报错的,仔细看了之后发现后端传过来一个\ u2027的一个字符,下面这样就直接报错了
    JSON.parse({"a":"\u2027"})

编码

文字算不算编码。盲文算不算编码。电报算不算编码。编码是用来交流/传递信息的一种方式/媒介,那么我们和计算机之间的信息传递自然也需要编码。

在计算机内部信息以电路的方式传递,通或不通代表1和0,所以基本上的信息单元(Binary digit)就是二进制的一位叫做比特(Bit)

我们输入的时候怎么让计算机知道我们输入的什么,或者计算机输出的时候我们也要知道他输出的什么,所以需要一种将二进制的信息转换成我们能理解的信息

ASCII

因为这种需求,在1967年美国公布了一套信息交换标准代码简称 ASCII 码,ASCII 码规定了一共128个字符,用的是7位编码,因为计算机是按8位存储的,所以 ASCII 前面一位一般都是0,所以一个 ASCII 码占8个二进制位,也就是一个字节(byte)。

对于英文做母语的人来讲 ASCII 是够了,但是还有很多还有很多中文,日文这种又怎么办呢

Unicode

1988年,几个大型的计算机公司一起研究了一字符集码叫做 Unicode,旨在为全世界所有的符号创造一个唯一的编码。

最开始 Unicode 使用的是2个字节也就是16位进行编码,可以表最少示 65 536 中不同的字符,后来发现不够,所以一个 Unicode 码至少需要占两个字节,那么都用 Unicode 码来传递存储的话会很浪费空间。

比如一个英文字符明明只需要1个字节就能存储,用 Unicode 的话就要2个字节,那么又人可能会想,那能用 ASCII 码的字符就用 ASCII,不能再用 Unicode 不就好了,但是计算机去读的时候怎么知道哪个字符是用 ASCII 哪个是用 Unicode。

UTF

就是因为这个原因所以出现了类似 UTF-8 这种编码方式,其他还有 UTF-16, UTF-32, GB2312 等等,但是其中 UTF-8 是用得最广的。

上面说的这些只是编码方式,和 Unicode 这种字符集的关系是,UTF-8 是 Unicode 的一种实现方式。也可以说是一种存储方式。

UTF-8 是一种变长的编码方式,可以使用1~4个字节表示一个符号,极大的减少空间浪费,他的编码原理简单点说是这样的

UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

乱码

乱码其实就是编码和解码的时候用了不同的字符集,比如我用UTF-8编码之后的字符你用GBK去解码,自然不能拿到我给你的字符,那么计算机是怎么知道他应该用什么编码方式来打开这个文件呢,一般是检测文件头,感觉前几个字符来匹配,猜测,实在不行就提示用户选择

有一个很奇怪的梗就是在记事本中输入联通然后保存关闭再打开,发现出现的是两个乱码,然后有人说这就是联通不如移动的原因。。

根本原因是在文本文件中输入中文的时候使用的是默认系统编码,在中文系统中是 GB 系列的编码,所以联调两个字在保存的时候会变成

c1 1100 0001 
aa 1010 1010 
cd 1100 1101 
a8 1010 1000 

第一二个字节和三四个字节正好符合 UTF-8 的编码格式,那么记事本会误认为是 UTF-8 的文件,所以就产生了乱码

/引子

\u2028的问题

在 Unicode 还没出来的时候,有两个团队不约而同的想搞一个统一的字符集,分别是1988年成立的 Unicode 团队和1989年成立的 UCS 团队,而且 UCS 团队先于 Unicode 团队在1990年公布了第一套编码方式 UCS-2 使用2个字节来表示已经有了的字符,然后在 1991 年两个团队觉得只需要一套就够了所以合并了字符集。比 UCS-2 更高级的编码方式是 1996 年诞生的 UTF-16,而且 UTF-16 是 UCS-2 的超集。

JavaScript 是1995年Brendan Eich用了10天设计的,那个时候只有 UCS-2 这一种编码方式可以用,所以在 JavaScript 中只能用 UCS-2 编码,造成了所有字符在这门语言中都是2个字节,如果是4个字节的字符会被当成2个字节处理

JSON 是一种语法来序列化一些对象,它基于 JavaScript 但不同的是

类型 区别
对象和数组 属性名称必须是双引号字符串;最后一个属性后不能有逗号。
数值 前导零是禁止的(在 JSON.stringify 零将被忽略,但在 JSON.parse 它将抛出 SyntaxError);小数点后必须至少有一位数字。
字符串 只有有限的一些字符可能会被转义;禁止某些控制字符;Unicode 行分隔符 (U+2028)和段分隔符 (U+2029)被允许 ; 字符串必须是双引号。请参见以下示例,其中 JSON.parse() 能够正常解析,但把它当作JavaScript解析时会抛出 SyntaxError 错误:let code = '"\u2028\u2029"';JSON.parse(code); // 工作正常eval(code); // 失败

简单的说JSON自身是支持这个特殊的 Unicode 字符的,但是在用 JavaScript 去执行 JSON 方法的时候,会把这些特殊的字符解析成换行,所以就报错了。这可能是JSON设计的时候的一个失误吧,导致他并不严格是 JavaScript 的一个子集。

参考

Unicode与JavaScript详解

字符编码笔记:ASCII,Unicode 和 UTF-8

程序员趣味读物:谈谈Unicode编码

JSON: The JavaScript subset that isn't

JSON