EmoArt
发布
术语表

什么是代理对 (Surrogate Pair)

最近更新: 2026-05-19·约 4 分钟

本文约 4 分钟读完。

输入一个 emoji 却被计为「2 个字符」。这背后的原因就是代理对。当用 UTF-16 这种较早的编码设计来处理 Unicode 的表示范围时,有些字符不得不用两个「半片」来表示。 这对「半片」的组合就叫做代理对。它是处理 emoji 和部分汉字时必然会遇到的基础概念。

定义

代理对是 UTF-16 中用两个 16 位单元来表示超过 U+FFFF 的 Unicode 码位的机制。 前半部分称为「高位代理」(High Surrogate, U+D800-U+DBFF),后半部分称为「低位代理」(Low Surrogate, U+DC00-U+DFFF), 两者必须成对出现。

为什么需要它

Unicode 拥有从 U+0000 到 U+10FFFF 的广阔码位空间,表示这些需要 21 位。 然而 UTF-16 每个单元只有 16 位,单独只能表示到 U+FFFF。 为了处理超出这个范围的字符 (大部分 emoji、部分汉字、古文字、数学符号等),引入了高位和低位组合的扩展机制。

与 emoji 的关系

emoji 的码位大多位于 U+1F000 以后的区域,在 UTF-16 中以代理对形式表示。 例如 🌸 (U+1F338) 在 UTF-16 中存储为 D83C DF38 两个 16 位单元。 这就是 JavaScript 中一个 emoji 的字符串长度为 2 的直接原因。

JavaScript 的行为

JavaScript 的 String 内部以 UTF-16 表示,因此 .length 会将代理对计为 2。

表达式结果含义
"あ".length1在 BMP 内,无需代理对
"🌸".length2由代理对构成
[..."🌸"].length1按码位单位分解
"🌸".codePointAt(0)127800组合后的码位值

与 UTF-8 / UTF-32 的区别

代理对是 UTF-16 特有的机制。UTF-8 用 1-4 字节的可变长度表示所有码位,UTF-32 用固定 4 字节处理所有码位。 在使用 UTF-8 或 UTF-32 的语言和处理系统中,不会出现代理对的概念。

实务中的应用场景

  • 字符数验证:当表单需要以「用户感知的字符单位」计数最大字符数时,需要理解代理对
  • 文本处理:子字符串截取、反转处理、正则表达式匹配中,需要将代理对作为一个单位处理
  • 文件名和数据库:旧系统无法正确处理代理对,导致包含 emoji 的数据损坏
  • 社交媒体字符计数:各平台计数逻辑的差异源于对代理对和字素簇的不同处理方式

正确计数的方法

如果想按用户认知的「1 个字符」为单位计数,需要以字素簇而非代理对为单位。 现代 JavaScript 中可以使用标准的 Intl.Segmenter,它能正确地将 emoji 和 ZWJ 序列视为 1 个单位。

常见误解

  • ❌「emoji 全部被算作 2 个字符」→ ✅ U+FFFF 以下的 emoji (部分旧符号类) 无需代理对,算 1 个字符
  • ❌「代理对在 UTF-8 中也会用到」→ ✅ 这是 UTF-16 特有的机制
  • ❌「高位代理单独也是有效字符」→ ✅ 单独出现时是无效的 Unicode 字符串 (Unpaired Surrogate)

相关术语

  • Unicode - 国际字符标准
  • Emoji - 大多以代理对形式表示的字符

这篇文章对你有帮助吗?