JavaScript 的 charAt() 方法处理 Emoji 时返回乱码,因其按 UTF-16 编码单元索引,而 Emoji 多为代理对或组合序列;应改用 for...of、Array.from()、[...str] 或 Intl.Segmenter 安全提取字符。

JavaScript 的 charAt() 方法在处理 Emoji 表情时可能返回“乱码”或“空字符”,根本原因是它按 UTF-16 编码单元(code unit)索引,而大多数 Emoji 是由多个 UTF-16 单元组成的代理对(surrogate pair)或更复杂的组合序列(如带肤色修饰符、ZWJ 序列)。
为什么 charAt 会“切开” Emoji
UTF-16 中,基础 BMP 字符(如 ASCII、常用汉字)占 2 字节(1 个 code unit),但大部分 Emoji(如 ?、??、???)超出 BMP,需用两个连续的 UTF-16 单元(即代理对)表示。例如:
-
'?'.length === 2(实际是 2 个 UTF-16 单元) -
'?'.charAt(0)返回高代理位(如'\ud83d'),不是有效字符 -
'?'.charAt(1)返回低代理位(如'\udc4b'),单独也无效
安全获取 Emoji 字符的替代方法
要正确提取第 n 个“用户感知的字符”(即 Unicode 码点或字形),应避免直接使用 charAt,改用以下方式:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
用扩展的 for...of 循环:自动按 Unicode 码点迭代,支持代理对和组合序列
for (const ch of 'Hello ????') { console.log(ch); } -
用 Array.from(str):将字符串转为真正按字符(而非 code unit)划分的数组
Array.from('??')[0] // '??'(完整带肤色 Emoji) -
用 [...str]:展开语法效果同上,简洁常用
[...'??'][0] // '??' -
用 Intl.Segmenter(现代推荐):按语言学“字素簇”(grapheme cluster)分割,最准确处理 ZWJ 序列、变音符号等
const seg = new Intl.Segmenter(); [...seg.segment('???❤️??')].map(s => s.segment)
兼容性与实际建议
若需支持旧环境(如 IE)且不依赖 polyfill,可结合 String.fromCodePoint() 和 codePointAt() 手动处理代理对:
立即学习“Java免费学习笔记(深入)”;
-
str.codePointAt(i)返回第 i 个 code unit 起始的完整码点(支持 >0xFFFF) - 配合
String.fromCodePoint()可重建字符:String.fromCodePoint(str.codePointAt(0)) - 注意:
codePointAt返回的是码点数值,需手动跳过后续代理单元(通常长度为 1 或 2)
日常开发中,优先使用 [...str] 或 Array.from(str);对复杂 Emoji 组合(如家庭表情 ????),强烈建议用 Intl.Segmenter。

















