目录
1. 介绍
在计算机科学中,字符的表示和存储涉及到两个重要的概念:代码点(Code Point)和代码单元(Code Unit)。它们是在处理文本数据时至关重要的概念。
1.1 码点(Code Point)
- 定义:码点是
Unicode
中的一个基本概念,它代表了字符集中的每个字符的唯一位置。 - 表示:码点通常用十六进制表示,并以
U+
开头,后面跟随一个或多个十六进制数字。例如,U+0041
表示拉丁字母“A” - 作用:码点是字符在字符集中的抽象位置,它们并不直接表示字符的存储形式或编码方式,而是作为字符的唯一标识。
1.2 码元(Code Unit)
- 定义:码元是计算机中存储和处理字符的最小单位。
- 特点:一个字符(或码点)可以由一个或多个码元组成,码元是构成编码方案的基本单位。
- 不同编码方案中的表示:在不同的编码方案中,一个字符可能由不同数量的码元组成。例如,在
UTF-8
编码中,一个字符可能由1 ~ 4
个字节(8 位)组成,而在UTF-16
编码中,一个字符通常由2
个字节(16 位)表示。
1.3 关系
码点与码元之间的关系是:一个字符(码点)可以由一个或多个码元组成,具体取决于使用的编码方案。不同编码方案下的字符编码和解码过程将码点映射到相应的码元序列,从而实现字符在计算机中的存储和处理。
2. codePointAt() 方法介绍
2.1 简介
codePointAt()
方法是 JavaScript
字符串对象的一个方法,用于返回给定索引处字符的Unicode
码点值(十进制数字)。
2.2 语法
str.codePointAt(index)
- 参数:
- str:要从中获取字符的字符串。
- index:一个整数,指定要返回其 Unicode 码点值的字符的索引。如果省略该参数,则默认为 0。
- 返回值:一个表示指定索引处字符的 Unicode 码点值的整数。
- 示例
let str = '😊';
let codePoint = str.codePointAt(0);
console.log(codePoint); // 输出 128522,对应 Unicode 码点 U+1F60A
2.3 注意事项
- 如果指定索引处的字符由两个
UTF-16
代码单元组成(即代理对),则返回代理对的码点值。 - 如果索引超出字符串长度范围,则返回
undefined
。 - 对于不支持该方法的旧版浏览器,可以使用
polyfill
进行兼容处理。
2.4 使用场景
- 获取字符串中特定位置字符的
Unicode
码点值(十进制数字),特别是处理包含Emoji
或其他Unicode
字符的字符串时。 - 在字符串处理、字符编码等方面的应用中,用于获取字符的
Unicode
码点值,进一步进行处理和分析。
3. 包含 Emoji 的字符串的视觉长度计算
Emoji
是一种符号字符,广泛用于表达情感、状态和概念。它们被纳入Unicode
标准,以便在不同的平台和设备上进行一致的显示和交流。Emoji
在Unicode
中的位置通常位于较高的码点范围内,这意味着它们的码点值较大。
在JavaScript
中,要计算包含Emoji
的字符串的码点数量,通常可以使用字符串的length
属性。然而,需要注意的是,对于包含代理对(surrogate pairs)的Emoji
,length
属性返回的值可能不准确,因为JavaScript
中的length
属性返回的是字符串中的字符数量,而不是码点数量。
3.1 使用 codePointAt() 计算
字符串中确保每个字符(包括Emoji
)都被计算为一个单位,我们需要对codePointAt()
返回的结果进行处理。当返回的码点大于 65535
时,我们需要将其作为一个字符处理,并且需要额外的操作来跳过低位区的码点。
const getCodePointLength = (str) => {
let len = 0;
for (let i = 0; i < str.length;) {
len++;
const codePoint = str.codePointAt(i) ?? 0
i += codePoint > 65535 ? 2 : 1;
}
return len;
};
getCodePointLength('123😂abc😀'); // 8
3.2 使用 Array.from() 计算
当你使用Array.from()
方法时,它会将字符串中的每个 Unicode
字符(包括 emoji
)转换为一个独立的 JavaScript
字符串。因此,如果一个 emoji
由多个 UTF-16
编码的字符组成,Array.from()
将会将其拆分为多个独立的 JavaScript
字符串。
const codePointArray = Array.from('123😂abc😀'); // ["1", "2", "3", "😂", "a", "b", "c", "😀"];
codePointArray.length() // 8
3.3 组合 Emoji 表情的长度计算
组合 Emoji 表情的长度计算在实际情况中需要特别注意,因为这些表情通常由多个字符组成,包括实际的表情符号以及连接符。在计算长度时,我们需要注意这些连接符。如果只要求不在截取字符串时,不显示乱码,那么连接符的问题可以忽略。
- Emoji 表情连接符
-
零宽度连接器(Zero Width Joiner,ZWJ): ZWJ 的
Unicode
码点为U+200D
(code point8205
)。它主要用于将多个Emoji
字符连接在一起,以创建新的序列。这种连接器在创建人物、家庭和职业等多个 Emoji 组合的情况下很常见。例如,将男性和女性的 Emoji 符号连接在一起以形成一个家庭的图像。 -
变体选择器-16(Variation Selector-16,VS16): VS16 的 Unicode 码点范围为 U+FE00 至 U+FE0F。它们用于指定与基本 Emoji 字符具有不同样式或变体的图形。例如,VS16 可以用于指定彩色或黑白版本的 Emoji 符号。
-
例如:
Array.from('abc👩👩👧👧'); // ['a', 'b', 'c', '👩', '', '👩', '', '👧', '', '👧']
其中,数组中的空字符并不是真正的空字符,事实上,它被称为零宽度连接器。不管我们对该数组如何截取,都不会出现乱码的情况,尽管视觉长度不统一。如果严格要求视觉长度的统一,需要研究emoji造字法来进行计算。
附:七种 emoji 造字法
一共有七种 emoji 造字法
- 基础 emoji,单个码点表示一个emoji 🧛 U+1F9DB
- 单个码点 + 变体选择符 ⚛️ = ⚛︎ U+26A0 + U+FE0F
- 皮肤修饰符 🤵🏽 = 🤵 U+1F935 + 🏽 U+1F3FD
- ZWJ 连接符 👨💻 = 👨 + ZWJ + 💻
- 旗帜符号 🇨🇳 = 🇨 + 🇳
- 标签序列 🏴 = 🏴 + gbsct + U+E007F
- 键位序列 *️⃣ = * + U+FE0F + U+20E3
前四种方法也可以组合使用,可构造非常复杂的 emoji,例如
U+1F6B5 🚵 个人山地骑行
+U+1F3FB 浅色皮肤
+U+200D ZWJ
+U+2640 ♀️女性标志
+U+FE0F 变体标志
= 🚵🏻♀️ 浅色皮肤的女性山地骑行
4. 参考
- 使用零宽度连接器组合的emoji的一览表: https://unicode.org/Public/emoji/15.0/emoji-zwj-sequences.txt?
- 变体选择器-16 一览表:https://codepoints.net/variation_selectors
- Unicode Emoji 正则:https://www.unicode.org/reports/tr51/#EBNF_and_Regex