从 charCodeAt() 返回的到底是啥问起,到理解何为字符编码

1,152 阅读7分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

过年放假这几天看了些关于前端加密解密的资料,里面有提到了 charCodeAt() 这个方法,说是可以获取字符串的 ASCII 字符码点。但是 MDN 文档对该方法的介绍的第一句话是:

返回 0 到 65535 之间的整数,表示给定索引处的 UTF-16 代码单元

那么问题就来了,charCodeAt() 到底返回的是啥?ASCII 和 UTF-16 之间又有啥联系?另外,在新建 html 页面时,在 <head> 标签里,一般代码编辑器都会默认帮我们写上这么一句 <meta charset="utf-8" />,用于规定 html 文档的字符编码。那么到底什么是字符编码? utf-8 又是什么呢?下面,就带着这些问题,开始我们的研究之旅吧~

ASCII

我们编写的代码,其实就是一段文本,计算机在存储时,都需要经过某种方式转换成计算机能直接识别的诸如 010101 这样的二进制数字。最开始美国人发明计算机那会,他们会用到的文本,统共也就是 26 个英文字母(包括大小写就是 56 个)、0 到 9 这 10 个数字,以及常用的英文符号(包括空格)这 3 种可见字符,以及空字符、换行、回车等控制字符而已。
2022-02-04_122044.png
于是在 1963 年,美国国家标准学会(ANSI)就推出了 ASCII(美国信息交换标准代码)作为计算机及其它设备的文本字符编码标准。ASCII 将上面提到的所有可见字符及不可见字符一个个全部罗列进行编号,比如 0 代表“空字符”,48 代表数字“0”,65 代表大写英文字母“A”,97 代表小写英文字母“a”等,总共 128 个。0、48、65 这些编号,就叫做码点,或者叫码位。码点与码点代表的字符的集合,叫做字符集,这 128 个字符集合就是 ASCII 字符集。美国人直接将码点转换成二进制信息(ASCII 码)存储到计算机。这里有个问题。最大的码点为 127,那么转换成二进制就是 1111111,只有 7 bit,而计算机一般是以 8 bit(1 字节)为基本单位进行读写的,所以这些码点转成二进制后都会在前面加一位 0,也就是 127 会转为 01111111 存储。
2022-02-04_122304.png
注意,字符不一定会以对应的码点被存储在计算机里,从字符到计算机能够存储的内容之间的映射,才叫做编码。最简单的就是将字符对应的码点直接以二进制存储进计算机。

GB2312 与 GBK

由于 ASCII 码以 8 bit 表示 1 个字符,而 8 bit 最多能表示 256 个字符,给欧美用可能够了,但给我们中国用,连甲骨文都远远表示不全。那咋整呢?为了解决中文存储的问题,我们国家设计了一套以 16 bit 表示一个字符,采用分区管理的字符集 —— GB2312 字符集,转换成二进制也就是 GB2312 码。所谓分区管理,就是将一共 8836 个码位分配到 94 个区,每个区包含 94 个位。各个区的情况如下:

  • 01-09 区:收录除汉字外的 682 个字符;
  • 10-15 区:空白区,未有编码;
  • 16-55 区:收录 3755 个一级汉字,也就是常见汉字,按拼音排序;
  • 56-87 区:收录 3008 个二级汉字,基本就是些我们不太认得的字,按部首/笔画排序;
  • 88-94 区:也是空白区,未有编码; 仔细看的话不难发现,GB2312 字符集只有 16-87 区收录的是汉字,加起来总共有 6763 个汉字,这对于博大精深历史悠久的汉字而言显然还是不够用的,于是就有了对 GB2312 进行扩展的 GBK,收录了简繁体字、日文和韩文等。

Unicode

美国有 ASCII 码,中国大陆有 GBK 码,各个国家都用自己设计的一套编码,计算机内存里同一个数字,在不同的字符集里,代表的字符各不相同,乱码问题就来了。于是 Unicode(统一码),也叫万国码、单一码就诞生了,它作为一种标准,包含了字符集及对应的编码规则。其目的可以引用在其官网首页上的这么一句话:

Everyone in the world should be able to use their own language on phones and computers.

image.png即让世界上所有人都能在他们的手机或电脑上使用自己语言。这就需要将世界上所有的字符都集合在一起,然后给它们一个个编号(码点)。如今 unicode 连 emoji 都包括进去了。在 js 中,我们可以使用 codePointAt() 方法来查询 unicode 字符的码点。

UTF-32、UTF-8 和 UTF-16

一开始的时候,unicode 使用的是 UCS-2 字符集,采用 16bit(2 个字节) 编码系统,一共可以表示 65536 个字符。其将字符按顺序排列,依次标上对应的码点,然后与 ASCII 一样直接将码点转换成对应的二进制信息存储到计算机。后来,人们发现,65536 个字符不足以囊括世界上所有的字符,于是又有了 UCS-4 字符集,也就是 UTF-32。它用 32 bit,即 4 字节表示一个字符,位数不够就在前面补 0,总共可表示的字符近 43 亿个字符。但每个字符都需要用 4 字节,这在空间上效率就很低,比如 ASCII 编码的每个英文字母明明只需要 1 字节就能表示,意味着同样内容的英文文本,utf-32 需要的空间是 ASCII 的 4 倍 。为了解决 utf-32 空间效率上的问题,UTF-8 应运而生。UTF-8 是种针对 unicode 的可变长度编码规则。所谓的可变,指的是与 utf-32 编码后的长度固定为 32 bit 不同, utf-8 针对不同的字符,将 utf-32 字符集的码点划分成了如下 4 个区间,编码后的长度可以是 8 bit、16 bit、24 bit 或 32 bit: 2022-02-04_123012.png 说明:

  • 诸如规定以 1110 开头,是为了让计算机知道怎么划分字符,1110 开头就表示自己和后面 2 个字节(以 10 开头)表示的是 1 个字符;
  • x、y、z、a 表示的是 0 或 1,之所以用不同字母表示,是为了表达对应的二进制码点转到 utf-8 编码是如何拆分的。 utf-8 兼容了 ASCII,unicode 的前 128 个字符及码点与 ASCII 的一模一样,用 utf-8 编码后,也都是 1 字节,二进制信息一样。

我们再来说下何为 UTF-16。UTF 是英文 unicode transformation format 的首字母缩写,直译成中文就是 unicode 转换格式。可以这么理解,unicode 负责把字符一个个编好号,无论是 UCS-2 的 65536 个字符还是 UCS-4 中数量更多的字符,让每个字符都有自己对应的码点。而怎么把这些码点信息转换成 01 串存储进计算机,不同的方式,就有了 utf-8、utf-16 或是 utf-32。utf-16 就是将所有 unicode 字符对应的码点都用 16 bit(2 字节)来保存,因此,无法兼容 ASCII 编码。

最后,回到最初的问题,charCodeAt() 返回的是啥?相信读到现在已经不难回答了,对于 unicode 字符集开头的 128 个字符,返回的既可以说是 UTF-16 代码单元,也可以说是 ASCII 字符码点,因为它们是一样的。

感谢.gif 点赞.png