先记住这个答案
TextDecoder 默认是容错的:遇到不合法的 UTF-8 字节,它把这段字节替换成 U+FFFD 替换字符(就是那个黑色菱形里带问号的 �),然后继续往下解码,不会抛错。构造时传 { fatal: true } 就切换到严格模式,遇到坏字节直接抛 TypeError。另一个选项 ignoreBOM 管的是文本开头的 BOM 标记,和坏字节处理无关,而且它的名字很容易理解反。
- 默认把坏字节替换成 �,不抛错
fatal: true遇到坏字节抛TypeErrorignoreBOM: true是保留 BOM,不是删掉 BOM- 编码选错时
fatal只能报错,不能帮你猜对
默认模式和严格模式的区别
先造一段坏数据:0xff 在 UTF-8 里永远不可能出现,把它夹在 A 和 B 中间。
const bytes = new Uint8Array([0x41, 0xff, 0x42]) // A、非法字节、B
console.log(new TextDecoder().decode(bytes)) // 'A�B',坏字节被替换
const strict = new TextDecoder('utf-8', { fatal: true })
try {
strict.decode(bytes)
} catch (err) {
console.log(err.name) // 'TypeError'
}默认模式把 0xff 换成 �,前后的 A 和 B 都保住了。严格模式一个字符都不返回,直接抛错。
选哪种要看场景。展示聊天记录、日志、用户上传的文本预览时,用默认模式,一个坏字节不该让整页内容都显示不出来。导入配置文件、解析协议、校验签名数据时,用 fatal: true,因为静默替换会把损坏藏起来:数据坏了,程序却以为一切正常,等到后面某个字段对不上才发现,很难追到源头。
- 截断的字符也算坏字节
- 除了
0xff这种本来就非法的字节,最常见的是被截断的多字节字符。比如€是E2 82 AC三个字节,只给前两个E2 82,默认模式输出一个 �,严格模式抛TypeError。如果是流式解码传了stream: true,解码器会先把这两个字节缓存着,等到流结束那次decode()还凑不齐,才按上面的规则处理。
ignoreBOM 为什么容易理解反
BOM 是放在文本最开头的几个字节,用来标记编码和字节序。UTF-8 的 BOM 是 EF BB BF,Windows 记事本早年保存 UTF-8 文件时常会带上它。
- 名字说的是「忽略 BOM 的特殊处理」
- 默认
ignoreBOM: false时,解码器认出开头的 BOM 后会把它吃掉,结果里只有正文。设成true表示忽略 BOM 的特殊含义,把它当成普通字符U+FEFF留在结果里。所以带 BOM 的EF BB BF 41默认解出来是'A',长度 1;ignoreBOM: true解出来长度是 2,第一个是看不见的U+FEFF。 这个字符看不见但真实存在。读 CSV 时,第一列的表头会变成'\uFEFFname',用row.name取值拿到undefined,是很经典的坑。大多数情况保持默认就对了。
容易答错的地方
- 以为
ignoreBOM: true是删掉 BOM - 正好相反。默认就会删,设成
true才会保留。 - 以为
fatal能识别编码错误 - 一个 GBK 文件用 UTF-8 去解,
fatal: true大概率会抛错,但它只能告诉你「这不是合法的 UTF-8」,不会告诉你正确的编码是什么。而且有些字节序列恰好在两种编码下都合法,这时不报错,只会得到一串错的文字。编码应该来自 HTTP 的Content-Type、文件格式约定这类可信来源,而不是靠fatal去试。 - 用默认模式判断数据是否完整
- 默认模式下损坏的数据也能解码成功,返回的字符串里多几个 �。想确认数据没坏,必须开
fatal,或者自己检查结果里有没有�。
面试官还会怎么问?
TextDecoder 能解 UTF-16 吗?
能。构造时传 'utf-16le' 或 'utf-16be',区别是字节序:「你」的码点是 U+4F60,小端存成 60 4F,大端存成 4F 60。两个选项 fatal 和 ignoreBOM 在 UTF-16 下一样生效,UTF-16 的 BOM 是 FF FE(小端)或 FE FF(大端)。除了 UTF-8 和 UTF-16,它还支持 GBK、Big5 等常见编码。
替换字符的个数能反推出坏了几个字节吗?
不能精确反推。解码算法按规则把非法序列分段,每段一个 �。一个孤立的 0xff 是一个 �,一个被截断的三字节字符的前两个字节合起来也只是一个 �。所以 � 的数量和坏字节的数量没有固定比例。
严格模式下怎么只跳过坏的那部分?
fatal 是全有全无的,抛错后拿不到任何结果。想做到「记录错误但保留其余内容」,就用默认模式解码,然后在结果里搜 � 统计位置,自己决定是告警还是拒绝。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。