要知道 Emoji 中的编码,我们就得先知道「Unicode」这个玩意,在官方网站中有这么一句话:「Unicode 为所有的字符都提供了一个独一无二的数字,不论在哪个平台,不论在什么项目,不论是怎么语言。」

Unicode provides a unique number for every character, no matter what the platform, no matter what the program, no matter what the language. -- Unicode Information

什么是 Unicode?

最简单的来说,他就是使用英文+数字+"+"来表示我们日常的字符的,譬如说这个文章我们就可以使用 Unicode 全部写出来,当然就不是给人看的了,为什么?

Unicode 标准定义了一个 code space,一组从 0 到 10FFFF1 范围的数值被称为「代码点」, 并且会表示为 U+0000 到 U+10FFFF

「U+」后面跟的十六进制的代码点值,前面有前导零2,代码点值至少四位数

比如说 U+00F7 表示 ÷ ,代码点 U+0041 是十六进制数 0041(等于十进制数 65)代表 Unicode 标准中的字符“A”,而 U+13254 (不是 U+013254,没有前导零)表示的是某一个埃及象形文字

但是在众多代码点中,从 U+D800 到 U+DFFF 的代码点,是不允许编码有效字符的,它将被 Unicode 保留。

U+D800–U+DBFF(1024 个代码点)范围内的代码点称为高代理代码点,而 U+DC00–U+DFFF(1024 个代码点)范围内的代码点称为低代理代码点码点。1高1低在 UTF-16 中形成一个代理对,以此来表示大于 U+FFFF 的代码点。

在排除代理项和非字符后,有 1,111,998 个代码点可以使用。

UTF-X 都是些什么?

UTF-8 是一种变长的 Unicode 编码方式,它可以使用 1 到 4 个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于 n 字节的符号(n > 1),第一个字节的前 n 位都设为1,第 n + 1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

UTF-8 编码的一个例子:

Unicode 码点UTF-8 编码(二进制)UTF-8 编码(十六进制)
U+00000000000000
U+00070000011107
U+00410100000141

UTF-16 是一种 Unicode 的编码方式,它使用 2 或 4 个字节来表示一个符号(字符)。UTF-16实现方案则介于UTF-8和UTF-32之间。对最常用的基本平面中字符的存储空间进行了压缩,使得汉字只需要两个字节就可以存储。

但是UTF-16又是怎么解决字符存储的时不同字符的边界问题的?这就需要用到代理对的概念了。

前面提到代理对的概念。UTF-16 将代理对的第一个代码点的值减去 0xD800,将第二个代码点的值减去 0xDC00,然后将两个结果合并,得到一个 20 位的二进制数,加上 0x10000,就得到了原来的代码点的值。

UTF-16 编码的一个例子:

Unicode 码点UTF-16 编码(二进制)UTF-16 编码(十六进制)
U+000000000000000000000000
U+000700000000000001110007
U+004100000000010000010041

这样,UTF-16 就可以使用 2 个字节来表示 U+0000 到 U+FFFF 之间的字符,而对于 U+10000 到 U+10FFFF 之间的字符,就需要使用 4 个字节来表示了。这样就可以保证所有的字符都可以被编码,而且编码后的字节长度也是固定的。

当然,除 Unicode 以外,还有很多不同的编码标准(如 GBK 等)但本文不详细说他们了。

讲讲 Emoji 的编码

Emoji 我们日常都会使用,比如 😊 它们都是 Unicode UTF-16 编码的,在 UTF-8 编码中,它们会被编码成 8 个字节,这样的编码方式就会导致 Emoji 的存储空间变大。

截屏2022-10-31 08.41.33

从U+1F300开始,存放的这些小表情就是emoji表情。

但需要注意的是,Unicode 本身是不支持 Emoji 的,Emoji 是由 Unicode 和 Emoji 两部分组成的,Unicode 只是规定了 Emoji 的编码,而 Emoji 是由 Apple、Google 等公司自己定义的。

如果用户的系统没有实现某个emoji表情的渲染,就会显示成一个空方框。

笑脸白发男子撅嘴的女人
一个码位三个码位四个码位

我们看到,一个 emoji 表情是变长存储的,而且一个表情可占用多个码位组成。而其中这个白发男人,是由三个码位组成: U+1F468 U+200D U+1F9B3,而这个白发男人的 emoji 标签是 People & BodyPerson。这表明,U+1F468 表示的是人,U+200D 表示的是连接符,U+1F9B3 表示的是白发男人。这些表情的渲染又有什么规则呢?

在 Unicode 官方文档中,有提到相关的内容 我简单给大家总结一下:

  1. 一个 emoji 表情的渲染是由多个码位组成的,这些码位的值是连续的,且值的范围是在 U+1F300 到 U+1F5FF 之间的。
  2. emoji 标签由两部分组成,第一部分是 emoji 的类型,第二部分是 emoji 的子类型,比如 😊 的 emoji 标签是 Smileys & EmotionFace Smiling。 emoji 标签的具体内容可以参考这里

有一个很有趣的现象,就是 emoji 表情的渲染是由多个码位组成的,但是这些码位的值是连续的,且值的范围是在 U+1F300 到 U+1F5FF 之间的。这样的设计,就导致了 emoji 表情的渲染是有规律的,比如我们可以通过下面的代码来生成 emoji 表情:

function generateEmoji() {
  const start = 0x1f300;
  const end = 0x1f5ff;
  const code = Math.floor(Math.random() * (end - start + 1)) + start;
  return String.fromCodePoint(code);
}

甚至,你可以用 js 的 replace 方法来替换 emoji 表情,假设我们要替换一下 👨‍👩‍👧‍👧 中的孩子,要把它变 👨‍👩‍👧‍👧 ,我们可以这样做:

'👨‍👩‍👧‍👧'.replace('👦', '👧'); // 👨‍👩‍👧‍👧

参考材料

Footnotes

  1. 此处使用的是十六进制数字系统,它使用 16 个不同的符号,最常见的是符号“0” - “9”表示值 0 到 9,“A”-“F”表示值从 10 到 15。

  2. 前导零是位置表示法中数字字符串中第一个非零数字之前的任何 0 数字。例如,詹姆斯邦德的著名标识符 007 有两个前导零。