前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库charAt charCodeAt codePointAt 区别
JavaScript值与类型

charAt、charCodeAt 和 codePointAt 有什么区别?处理 emoji 该用哪个?

charAt(i) 返回第 i 个码元组成的字符串,charCodeAt(i) 返回这个码元的数值(0~65535),codePointAt(i) 返回从这里开始的完整码点。emoji 占两个码元,前两个会把它劈成两半,所以处理 emoji 要用 codePointAt 配合 for...of 或展开运算符。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#值与类型
先看核心答案读代码示例
理解线索

码元、码点和字形

  1. 码元UTF-16 的 16 位单元,字符串下标、length、charAt 都以它为单位
  2. 码点Unicode 给每个字符的编号,大于 0xFFFF 时要用两个码元(代理对)表示
  3. 字形簇用户眼里的「一个字」,可能由多个码点组合而成,比如带肤色的 emoji

codePointAt(i) 的 i 仍然是码元下标,指向代理对的后半个时,只会返回那半个的数值。

核心回答

先记住这个答案

JavaScript 字符串内部是 UTF-16 编码,下标和 length 数的是 16 位的码元,不是用户看到的字符。常用汉字和英文占一个码元,emoji 和一些生僻字占两个。charAt(i) 返回第 i 个码元组成的单字符字符串,charCodeAt(i) 返回这个码元的数值,两者遇到 emoji 都只能拿到一半。codePointAt(i) 如果发现 i 处是代理对的前半个,会把两个码元合起来返回完整的码点。处理 emoji 时用 codePointAt 取编号,用 for...of 或 [...str] 按字符遍历。

  • 字符串索引按 UTF-16 码元计算
  • emoji 等码点大于 0xFFFF 的字符占两个码元
  • codePointAt 可读完整 Unicode 码点
  • 按字符遍历用 for...of 或 [...str]

同一个 emoji,三个方法的结果

用一个中间夹着 emoji 的字符串,对比三个方法的返回值。

在 'a😀b' 上逐个调用JavaScript
const s = 'a😀b'
console.log(s.length)

console.log(s.charAt(0), s.charCodeAt(0), s.codePointAt(0))
console.log(JSON.stringify(s.charAt(1)))       // 只拿到前半个
console.log(s.charCodeAt(1), s.codePointAt(1)) // 半个 vs 完整
console.log(s.codePointAt(2))                  // 指向后半个

console.log([...s].length, [...s][1] === '😀')
console.log(String.fromCodePoint(128512) === '😀')
console.log(s.charAt(10) === '', s.charCodeAt(10), s.codePointAt(10))
查看输出与解释
4
a 97 97
"\ud83d"
55357 128512
56832
3 true
true
true NaN undefined

'a😀b' 看起来是 3 个字符,length 却是 4。😀 的码点是 U+1F600(十进制 128512),超出了一个码元能表示的范围,所以被拆成两个码元 0xD83D 和 0xDE00,这一对叫代理对。charAt(1) 拿到的是孤零零的前半个,显示出来是乱码。charCodeAt(1) 返回 55357,也只是前半个的值。codePointAt(1) 识别出这是代理对的开头,合并后返回 128512。 注意 codePointAt(2) 返回的是 56832,也就是后半个码元。它的参数仍然是码元下标,不会自动跳到字符边界。

按字符遍历和截取

字符串的迭代器是按码点走的,所以 for...of、[...s]、Array.from(s) 都能把 emoji 当作一个整体。需要按「字符数」限制长度、截断文本时,先展开成数组再操作,比如 [...s].slice(0, 10).join(''),就不会把 emoji 从中间切断。

越界时三个方法表现也不同:charAt 返回空字符串,charCodeAt 返回 NaN,codePointAt 返回 undefined。方括号访问 s[10] 和 codePointAt 一样返回 undefined。

回答前,多想一步

容易答错的地方

以为 str.length 就是字符数
'😀'.length 是 2。做输入框字数限制时直接用 length,用户输入 emoji 会被多算。按码点计数用 [...str].length。
以为 codePointAt 能解决所有 emoji 问题
很多 emoji 是几个码点组合出来的。👍🏽 是「竖大拇指」加「肤色修饰符」两个码点,👨‍👩‍👧 是三个人物码点加两个零宽连接符,共五个码点。[...str] 会把它们拆开。要按用户看到的字形切分,需要用 Intl.Segmenter,把 granularity 设为 'grapheme'。
用 String.fromCharCode 还原 emoji
fromCharCode 只接收码元,传 128512 会被截断成一个错误的字符。对应 codePointAt 的是 String.fromCodePoint。
试着用自己的话回答

面试官还会怎么问?

怎么判断字符串里有没有落单的代理项?

ES2024 新增了 str.isWellFormed(),返回 false 说明有落单的代理项,str.toWellFormed() 会把它们替换成替换字符 U+FFFD。截断字符串后发给后端之前可以检查一下,否则 encodeURIComponent 遇到落单代理项会抛 URIError。老环境不支持这两个方法时要先判断能力。

正则怎么正确匹配 emoji?

加 u 标志。不加时正则也按码元匹配,/^.$/.test('😀') 是 false;加上 u 后 . 能匹配一个完整码点,/^.$/u.test('😀') 是 true。\u{1F600} 这种写法也只有带 u 时才生效。

charAt(i) 和 str[i] 有什么区别?

正常下标时结果一样,都是按码元取。区别在越界:charAt 返回空字符串,方括号返回 undefined。另外 at(-1) 支持负数下标,但同样按码元,取不到结尾 emoji 的完整字符。

从一道题,走向一组知识

把知识连起来

值与类型

为什么 [] == ![] 是 true,而 [] == [] 是 false?

字符串比较时 == 不关心编码细节,但 [] == ![] 这类题的第一步也是把值转成字符串。

值与类型

用 == 比较字符串和数字时,转换规则是什么?

charCodeAt 返回数字,拿它和字符串比较时 == 会先把字符串转成数字,结果容易出乎意料。

参考资料

  • JavaScript:值与类型参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 同一个 emoji,三个方法的结果
  3. 按字符遍历和截取
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑