Emoji Unicode 速查表 - Emoji 表情符号编码大全
Emoji 在屏上看着正常,却常让数据库列存不下、或在 JSON/接口传输中变成乱码。本表按类别列出常用 Emoji 的码点,并解释代理对(surrogate pair)、ZWJ 联合序列(如家庭 Emoji 由多个码组合)、以及 UTF-8 存储长度这些坑。做前端显示、后端落库、跨平台传输时常来对照。读完能正确评估"这个 Emoji 要占几个码点/几字节"。
典型使用场景
面向做表情选择器、消息通知渲染、搜索过滤或去重统计的开发者。本表收录 48 个高频 emoji 的 Unicode 码位与名称:表情类(U+1F600 起)、手势类(U+1F44D 起)、动物、食物、运动与器物。核心价值在于码位本身——emoji 在 UTF-8 下占 4 字节、代理对在 JS 里是两个 code unit,这些都要求你记住确切码位才能做过滤、去重与长度计算;名称(如 grinning face)则是与 Unicode 标准对齐的检索关键词。
笑脸与情感 10
😀 U+1F600😂 U+1F602😊 U+1F60A😍 U+1F60D😎 U+1F60E😭 U+1F62D😡 U+1F621👍 U+1F44D👎 U+1F44E👏 U+1F44F手势 6
🙏 U+1F64F🤝 U+1F91D✌️ U+270C🤞 U+1F91E🤘 U+1F918👌 U+1F44C心形 6
❤️ U+2764💔 U+1F494💕 U+1F495💖 U+1F496💗 U+1F497💘 U+1F498动物 10
🐶 U+1F436🐱 U+1F431🐭 U+1F42D🐰 U+1F430🦊 U+1F98A🐻 U+1F43B🐼 U+1F43C🐨 U+1F428🐯 U+1F42F🦁 U+1F981食物 8
🍎 U+1F34E🍕 U+1F355🍔 U+1F354🍟 U+1F35F🌭 U+1F32D🍿 U+1F37F☕ U+2615🍺 U+1F37A活动与物品 8
⚽ U+26BD🏀 U+1F3C0🎮 U+1F3AE🎵 U+1F3B5🎉 U+1F389🎁 U+1F381📱 U+1F4F1💻 U+1F4BB参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
码位 U+XXXXX | emoji 的唯一标识,用于正则过滤、去重与数据库主键 | 👍 U+1F44D :: 点赞 thumbs up |
Emoji 分区范围 | 按码位区间快速判断某字符是否为 emoji,无需逐个枚举 | 😀 U+1F600 :: 笑脸 grinning face |
英文名称 | 与 Unicode 标准对齐的检索键,用于搜索框、标签系统与跨平台校验 | 🎵 U+1F3B5 :: 音符 musical note |
代理对占位 | 注意部分表内 emoji(如 ✌️ U+270C)带变体选择符,实际占两个码位 | ✌️ U+270C :: 胜利 victory hand |
语义分类 | 按表情/手势/动物/食物/运动分组,用于表情选择器的分区布局 | 🍕 U+1F355 :: 披萨 pizza |
易错点与避坑指南
现象JS 里 emoji.length 得到 2 而不是 1,字符串截断把表情切成乱码。
原因emoji 位于 BMP 之外,用两个 UTF-16 代理码元表示,length 数的是码元不是字符。
处置用 [...str] 或 Array.from(str) 迭代字符,或按码位用 codePointAt 取值后再截断。
现象用户输入的两个「看起来一样」的 emoji 被当成两个不同的值。
原因带变体选择符的 emoji(如 ✌️ U+270C)比不带选择符的多一个 U+FE0F,字形相同但码位不同。
处置做比较或去重前先归一化,剥离 U+FE0F 变体选择符再比对。
现象数据库列长度按字符数设成 10,实际存不进一个完整表情序列。
原因utf8mb4 下一个 emoji 占 4 字节,且 varchar 长度在部分数据库里按字符计、按字节截断。
处置统一使用 utf8mb4 字符集,并为含 emoji 的列预留足够长度。
现象通知里的 emoji 在部分平台显示成方框。
原因目标字体或平台缺少该码位的字形,缺字形即渲染为豆腐块。
处置优先选用覆盖最广的基础 emoji(本表情类表内条目),并为关键通知准备文字回退。
排障路径
1查一个 emoji 的实际码位与码元长度
node -e "const s=process.argv[1];console.log([...s].map(c=>c.codePointAt(0).toString(16)))" "👍"输出 1f44d 说明它是单码点;若出现两个值则是代理对或带变体选择符。
2判断字符串里是否含表内表情区间字符
node -e "console.log(/[\u{1F300}-\u{1FAFF}]/u.test(process.argv[1]))" "hi 👍"true 表示落在表情区间;带 u 标志才能正确处理代理对。
命令示例
看 emoji 的长度、码点与代理对
node -e "
const e = '\u{1F600}';
console.log('length', e.length, 'codePointAt', e.codePointAt(0).toString(16), 'charCodeAt', e.charCodeAt(0).toString(16));
console.log('fromCodePoint', String.fromCodePoint(0x1f600) === '\u{1F600}');
"length 2 说明它占一个代理对;charCodeAt(0) 返回的是高 surrogate 的 0xD83D,真正码位要靠 codePointAt 才能拿到。
输出
length 2 codePointAt 1f600 charCodeAt(0) d83d fromCodePoint true
带变体选择符的表情为何判不相等
node -e "
const a = '\u{1F600}', b = '\u{1F600}\u{FE0F}';
console.log(a === b, a.codePointAt(0) === b.codePointAt(0));
console.log([...b].map(c => c.codePointAt(0).toString(16)));
"两个字符串不等,但首个码位相同。做去重时要么按码位序列比较,要么先剥掉 FE0F 这类变体选择符。
输出
false true [ '1f600', 'fe0f' ]
用码位区间正则筛出文本里的 emoji
node -e "
const re = /[\u{1F300}-\u{1FAFF}]/u;
const t = 'ship \u{1F680} done\u{1F600} ok';
console.log([...t].filter(c => re.test(c)).join(' '));
console.log('count', [...t].filter(c => re.test(c)).length);
"u 标志让区间按码点解释,缺了它就会把代理对拆开匹配。展开运算符保证按码点而非 code unit 遍历。
输出
🚀 😀 count 2
确认每个 emoji 的 UTF-8 字节数
python -c "
for c in ['\U0001F600', '\U0001F44D', '\u2764']:
print(hex(ord(c)), len(c.encode('utf-8')))
"补充平面区码位占 4 字节,BMP 里的符号只占 3 字节——按 4 字节为每个表情留列宽会浪费空间,按 3 字节算则会溢出。
输出
0x1f600 4 0x1f44d 4 0x2764 3
常见坑与注意事项
- 不要用 emoji 的 length 当字符数:代理对让 length 恒为 2,截断会切出半个乱码,需要按显示宽度切就用 Intl.Segmenter。
- 用户输入里两个「看起来一样」的表情可能码位序列不同(带不带 U+FE0F 变体选择符),去重前先归一化,否则统计会翻倍。
- MySQL 必须用 utf8mb4 才能存补充平面码位,旧的 utf8 三字节字符集会让 U+1F600 直接写入失败。
- 按码位区间写过滤正则时务必加 u 标志,缺少它会按 code unit 匹配,把代理对拆成两个无意义字符。
- 数据库列宽别按「一个表情一个字符」估算:肤色修饰符与变体选择符叠加后可达十余个码元,留不出余量就会静默截断。
提示
- Emoji 是 4 字节 UTF-8,MySQL 需要用 utf8mb4 字符集才能存储。
- 不同系统显示的 Emoji 样式可能不同(iOS、Android、Windows 各有自己的字体)。
- JavaScript 中可以用 String.fromCodePoint(0x1F600) 生成 Emoji。
- 某些 Emoji 由多个 Unicode 组合(如肤色、性别),长度可能超过 2。
- 正则匹配 Emoji 需要用 \p{Emoji} 或专门的 Emoji 正则库。
常见问题
为什么 JavaScript 里 emoji 的 length 是 2?
因为 JS 字符串按 UTF-16 code unit 计数,而 U+1F600 落在 BMP 之外,必须用代理对表示:高 surrogate 0xD83D 加低 surrogate 0xDE00,正好两个 code unit。按「用户看到的一个字符」计数要用码点遍历(展开运算符)或 Intl.Segmenter;按显示宽度截断(例如截取前 10 个字符)则必须用 Segmenter,否则会把代理对劈开得到半个乱码。
U+2764 和 U+2764 加 U+FE0F 是同一个表情吗?
不是同一个码位序列,但渲染出来通常一样。U+2764 是红心本身,U+FE0F 是变体选择符,要求渲染器采用彩色(emoji)呈现而非单色文本样式。本表里的胜利手势(U+270C)与红心都带了这个选择符。后果是:字符串去重时 2764 与 2764 FE0F 判为不等,比较码点却相等——所以做 emoji 校验要按码位遍历,而不是把原始码位值直接当唯一键。
表里的 U+2615 和 U+26BD 为什么和大多数 emoji 差得远?
因为它们占的码位不同:绝大多数表情在 U+1F300 到 U+1FAFF 的补充平面区,UTF-8 占 4 字节;而 U+2615、U+26BD 落在 BMP 的杂项符号区,UTF-8 只占 3 字节。它们在早期标准里被定义为「带 emoji 呈现的文本符号」,所以往往也要带 U+FE0F 才稳定显示成彩色。做区间过滤时若只匹配补充平面,会漏掉这一类;长度假设也不能统一按 4 字节算。
用 emoji 长度当数据库列宽会出什么问题?
会静默截断或直接写不进去。一条看似一个字符的表情可能是代理对(2 个 code unit)再叠加肤色修饰符或变体选择符,最多可达十几个码元;用 VARCHAR(10) 按字符数定义时,UTF-8 字节数还会再放大四倍。最稳的做法是按 utf8mb4 建列并把宽度放大到最坏情况的余量,或者干脆用 TEXT / JSON 列并在应用层校验,而不是依赖数据库的长度语义。
怎么快速判断一串文本里到底有几个表情?
按码点遍历后逐个测正则,而不是按 length 相除。用展开运算符把字符串拆成码点序列,再用带 u 标志的表情区间正则逐个测试并计数,得到的就是真实的表情个数。这个方法同时躲开了代理对带来的 length 虚高问题,也是给通知内容做截断前必须做的一步。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。