为什么一定是�

590 阅读4分钟

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

前言

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

我们一起来学习字符串乱码 �!

为什么一定是�

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

上篇字符�之谜已经说了UTF-16的编码规则,和 "𢂘"编码后的结果。

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

但是不是所有的码点大于0xFFFF的字符在UTF-16编码下,ch.charAt(0)和ch.charAt(1)都是乱码呢?

这个又的回到了码点大于0xFFFF字符编码过程:

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

Unicode字符集的编码值范围为0 ~ 0x10FFFF, 大于0xFFFF的区间为 0x10000 ~0x10FFFF 减掉 0x10000, 其值的区间范围为 0x0000 ~ 0xFFFFF

  0x10000 ~ 0x10FFFF
  0x10000 ~  0x10000   
 ------------------- 减掉 0x10000
  0x00000 ~  0xFFFFF 

转为二进制

10位     低100000000000 0000000000  --- 0xF0000 
1111111111 1111111111  --- 0xFFFFF

上10位的取值区间:0000000000 ~ 1111111111 对应16进制 0x0000 ~ 0x03ff
下10位的取值区间:0000000000 ~ 1111111111 对应16进制 0x0000 ~ 0x03ff

第一码元,上10位加上 0xD800, 其值区间为:
0xD800 + 0x0000 ~ 0xD800 + 0x03ff = 0xD800 ~ 0xDBFF

'0x' + (0xD800 + 0x0000).toString(16).toUpperCase() // 0xD800
'0x' + (0xD800 + 0x03ff).toString(16).toUpperCase() // 0xDBFF

第二码元,下10位加上 0xDC00, 其值区间为:
0xDC00 + 0x0000 ~ 0xDC00 + 0x03ff = 0xDC00 ~ 0xDFFF

'0x' + (0xDC00 + 0x0000).toString(16).toUpperCase() // 0xDC00
'0x' + (0xDC00 + 0x03ff).toString(16).toUpperCase() // 0xDFFF

简单总结一下:

第一码元值区间: 0xD800 ~ 0xDBFF
第二码元值区间: 0xDC00 ~ 0xDFFF

Unicode的代理区 0xD800-0xDFFF, 其不代表任何字符。

image.png

第一码元和第二码元的值,死死的卡在代理区,必然是乱码。 所以码点大于0xFFFF的字符。

  1. 其长度值为2
  2. 采用索引值取值,必然两个都是乱码
  3. charAt也必然是乱码

这下你应该知道了吧,为什么一定会是乱码。

0xD800-0xDFFF

不知不说UTF-16编码的思路真的是妙不可言, 我这是顺方向推导。 其设计之初 的0xD8000xDC00是怎么被推导出来的呢?

大于0xFFFF的码点,减去0x10000区间 0x0000 ~ 0xFFFFF

需要四字节保存,计算过程参考前面为什么一定是�

前四值区间: 0x0000 ~ 0x03ff
后四值区间: 0x0000 ~ 0x03ff

0x03ff // 1023
0xDFFF -  0xD800 // 2047

到这里就很有意思了,代理区可用的码点是2047,差不多刚好是0x03ff(1023)的两倍。

刚好前四个字节和后四个字节需要的码点是 2046个,

代理区 (最大值 - 最小值)/2 + 最小值

'0x' + ((0xDFFF - 0xD800) / 2  + 0xD800).toString(16).toUpperCase() // '0xDBFF.8'

这里就差不多明白了吧,我真的是个小机灵鬼。

小结

今天你收获了吗?