字符�之谜

313 阅读3分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

前言

ECMAScript 6.0 简称ES6 , 是 JavaScript 语言的新一代的标准,于在 2015 年 6 月发布,正式名称就是《ECMAScript 2015 标准》。一般情况,泛指, 5.1版以后的标准,涵盖了 ES2015、ES2016、ES2017、ES2018、ES2019、ES2020、ES2021 等等

我们一起来解开字符串�乱码之迷。

�乱码

我们用for遍历字符串, 会出现如下的乱码。

至于如何正确的遍历和取值,可以参见我的另一篇文章 字符串的遍历。

var text = "𢂘a𠮷人";
for(let i= 0; i< text.length; i++){
    console.log(text[i]);
}
// �
// �
// a
// �
// �
// 人

Unicode简介

Unicode是一种字符集,Unicode是为整合全世界的所有语言文字而诞生的。没有规定应该如何存储, 所以才会有UTF-8, UTF-16, UTF-32等字符编码实现。

Unicode的编码空间从U+0000U+10FFFF,共有1,112,064个码位可用来映射字符。

Unicode的编码空间可以划分为17个平面,每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016到1016,共计17个平面。

第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),其他平面称为辅助平面

基本多语言平面内,从U+D800U+DFFF之间的码位区段是永久保留不映射到Unicode字符。

UTF-16字符编码

Unicode字符集的编码值范围为0-0x10FFFF,大于等于0x10000的辅助平面区的编码值无法用2个字节来表示。 因为一个字节8个bit位,2个字节16个bit位,一位16进制,需要四个bit来表示, 最大能表示 0xFFFF。

UTF-16利用Unicode的代理区0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

在此之前,先提一下 码元
码元(Code Unit,也称代码单元)是指一个已编码的文本中具有最短的比特组合的单元。对于UTF-8来说,码元是8比特长;对于UTF-16来说,码元是16比特长;对于UTF-32来说,码元是32比特长。

UTF-16编码过程:

  1. 码位减去 0x10000,得到的值的范围为20比特长的 0...0xFFFFF,不足的话,前面补0喽。
  2. 高位的10比特的值加上 0xD800 得到第一个码元
  3. 低位的10比特的值加上 0xDC00 得到第二个码元

我们以𢂘为例: 其码位为 0x22098

"𢂘".codePointAt(0).toString(16) // 22098
  1. 码点减去0x100000x22098-0x10000 = 0x12098 = 0001001000 0010011000
  2. 分割上10位和下10位, 上:0001001000 , 下: 0010011000
  3. 上10位 + 0xD800, 0001001000 + 0xD800 = 0x48 + 0xD800 = 0xD848
  4. 下10位 + 0xDC00, 0010011000 + 0xDC00 = 0x98 + 0xDC00 = 0xDC98

我们怎么验证结果呢?

var ch = "𢂘";
ch.charAt(0) // '\uD848'
ch.charAt(1) // '\uDC98

没错,完全一致。

U+D800U+DFFF 区间没有字符是没法打印的,故打印�。

接下来

接下来我们推导,为什么大于 0xFFFF的字符,打印一定是�。

小结

今天你收获了吗?

引用

Unicode
UTF-16
utf16编码格式