气死了,之前更详细一点的刚写完,没保存,不小心弄没了,搞不回来的那种。于是有了下面的这一概念罗列版。。。
现象
const str = '😷'
const str1 = str.split('').reverse().join('')
console.log(str.length) // 2
console.log(str1) // 乱码
原理探索
术语和概念
- 字符集:一堆字符组成的集合。
- 字符编码集:与字符集形成一一映射关系的指定集合,比如一个由一堆比特序列组成的集合,与每个字符对应的那个比特序列可称为抽象编码。
- Unicode:计算机科学领域里对世界上大部分文字进行整理、编码的一项业界标准。它定义了一套字符编码集,里面最多可容纳 17 * 2 ^ 16 = 1114112 个字符。
- 码位:Unicode 字符编码集里的抽象编码。
- 字符编码集的实现:将抽象编码进一步映射成一个以某一特定比特长度为单位的序列。
- 码元:上述特定比特长度代表的那个东西就是码元。
- 平面:Unicode 字符分为 17 组编排,每一组称为一个平面。
- 基本平面:编码空间范围为 U+0000 ~ U+D7FF 和 U+E000 ~ U+FFFF 对应的那个平面。不连续的地方分成 U+D800 ~ U+DBFF 和 U+DC00 ~ U+DFFF 两部分,不对应任何字符。
- 辅助平面:编码空间范围为 U+010000 ~ U+10FFFF 的那些平面,共 16 个。
- UTF-16 是一种 Unicode 字符编码集的实现方式,它将基本平面里的字符编码为 1 个码元,其余平面编码成 2 个(包含一个前导代理和一个后尾代理),一个码元为 16 比特,所以它最后的编码是不定长的。
- 前导代理:对应码位范围为
0xD800
~0xDBFF
(55296 ~ 56319)的码元,共 2 * 10 = 2048 个。 - 后尾代理:对应码位范围为
0xDC00
~0xDFFF
(56320 ~ 57343)的码元,共 2 * 10 = 2048 个。 - UTF-16 的编码算法:对于任意一给定码位
- 如果这个码位属于基本平面,返回码位对应的 16 比特的序列。
- 将码位减去
0x10000
得到一个属于0x00000
~0xFFFFF
内的 20 比特的序列,将高位的 10 比特(范围为0x0000
~0x03FF
)加上0xD800
得到前导代理(范围为0x0000
~0x03FF
),低位的 10 比特(范围为0xD800
~0xDBFF
)加上0xDC00
得到后尾代理(范围为0xDC00
~0xDFFF
),返回前导代理和后尾代理拼接成的 32 比特的序列,序列中依然是前导代理居高位,后尾代理居低位。
所以依次算法可知,对于任意一字符串,在 UTF-16 编码后生成的那串码元序列中,对任意一个码元进行判断就可以确定这个码元所对应的字符的边界,因为这个码元只有如下三种情况:
- 为前导代理
- 为后尾代理
- 对应基本平面里的某个字符
解释
😷应该是一个在辅助平面的字符,它被 UTF-16 编码成一个由两个码元组成的序列,第一个码元是前导代理,第二个是后尾代理:
console.log('😷'[0].charCodeAt()) // 55357
console.log('😷'[1].charCodeAt()) // 56887
参考: