前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库TextDecoder stream:true 分块解码多字节
JavaScript数据处理

分块解码时,TextDecoder 为什么要传 stream: true?

网络分块是按字节切的,一个汉字的 3 个字节可能被切到两块里。传 { stream: true } 是告诉解码器「后面还有数据」,它就会把末尾不完整的字节先存着,等下一块到了拼起来再输出。流结束时要再调一次不带参数的 decode() 收尾。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数据处理#缓存#流处理
先看核心答案读代码示例
理解线索

字节被切断时,解码器负责拼回去

  1. 多字节字符UTF-8 里汉字占 3 个字节,emoji 占 4 个,都可能跨 chunk
  2. stream: true末尾凑不成字符的字节先留在解码器里,不输出也不替换
  3. decode() 收尾不传 stream,告诉解码器流结束了,处理残留并重置状态

每个 chunk 都 new TextDecoder() 是最常见的写法错误。缓存存在实例里,换了实例,前一块留下的半个字符就丢了,传不传 stream: true 都没用。

核心回答

先记住这个答案

因为网络数据是按字节分块的,不管字符边界。UTF-8 里一个汉字占 3 个字节,完全可能前 2 个字节在这一块、第 3 个在下一块。不传 stream: true,解码器会认为数据到此为止,把那 2 个字节当成坏数据替换成 �。传了之后,它会把这 2 个字节先存在实例内部,等下一块来了拼成完整的字再输出。所以整条流要复用同一个解码器,最后再调一次不带参数的 decode() 收尾。

  • chunk 边界不等于字符边界,汉字可能被切成两半
  • stream: true 让解码器缓存末尾不完整的字节
  • 整条流必须复用同一个 TextDecoder 实例
  • 结束时调一次 decode() 把残留字节清掉

一个字被切成两块会怎样

欧元符号 € 的 UTF-8 是 E2 82 AC 三个字节。模拟网络把它切成两块,分别用两种方式解码。

不传 stream 和传 stream 的对比JavaScript
const first = new Uint8Array([0xe2, 0x82]) // € 的前两个字节
const second = new Uint8Array([0xac])       // € 的最后一个字节

// 错误写法:每块独立解码
const bad = new TextDecoder()
console.log(bad.decode(first) + bad.decode(second)) // '��',拼不回来了

// 正确写法:同一个实例,传 stream: true
const decoder = new TextDecoder()
let text = decoder.decode(first, { stream: true })  // '',先存着不输出
text += decoder.decode(second, { stream: true })    // '€',凑齐了
text += decoder.decode()                            // '',流结束,收尾
console.log(text) // '€'

错误写法里,第一块的 E2 82 凑不成字符,解码器直接输出一个替换字符 �;第二块的 AC 单独出现也不合法,又输出一个。字符串层面已经坏了,事后怎么拼都恢复不了原文。 正确写法里,第一次调用返回空字符串,因为两个字节还不够一个字。第二次调用时解码器把缓存的 E2 82 和新来的 AC 拼在一起,输出 €。

真实场景:流式读取 fetch 响应

AI 对话的打字机效果、大文件边下边显示,都是用 response.body.getReader() 一块块读字节。这时候解码器要放在循环外面创建,整条流共用一个。

读循环里怎么写
循环外 const decoder = new TextDecoder(),循环里每读到一块 value 就 decoder.decode(value, { stream: true }) 追加到结果上,done 为 true 跳出后再补一句 decoder.decode()。中文内容多的接口,如果把解码器写进了循环里,平时可能看不出问题,只有碰巧一个字跨了块边界才出现一个乱码,很难复现,排查起来也费劲。 也可以不自己写:response.body.pipeThrough(new TextDecoderStream()) 得到的就是文本流,它内部做的就是同一件事。
最后那次 decode() 为什么不能省
stream: true 的意思是「后面还有数据」。如果最后一块也这样传,解码器会一直等下去,末尾要是残留了不完整的字节,就永远不会被处理。不带参数调一次 decode() 等于告诉它「没有了」。这时残留的字节如果还凑不成字符,默认输出 �,开了 fatal: true 就抛 TypeError,之后解码器回到初始状态,可以复用来解码下一条流。
回答前,多想一步

容易答错的地方

每个 chunk 都新建一个解码器
缓存存在实例里。每块新建一个实例,前一块留下的半个字符就跟着旧实例一起丢了,这时传 stream: true 也救不回来。
先把字节转成字符串再拼接
有人想「先各自解码,最后把字符串拼起来」。问题在于解码那一步已经把半个字符替换成 � 了,信息丢了。要么流式解码,要么先把所有字节拼成一个 Uint8Array 再一次性解码。
以为字符完整了,一行数据就完整了
stream: true 只保证不会切坏一个字符,不保证一块刚好是一行 JSON 或一条 SSE 事件。上层协议的拆分要自己再缓冲一次。
试着用自己的话回答

面试官还会怎么问?

SSE 流为什么解码之后还要自己缓存?

一块数据可能包含半条事件,也可能包含好几条。常见做法是维护一个字符串 buffer,每次把解码后的文本追加进去,按空行 \n\n 切分,最后一段不完整的留在 buffer 里等下一块。解码器处理的是字节到字符,这一步处理的是字符到消息。

TextDecoderStream 和手动 decode 有什么区别?

效果一样。TextDecoderStream 是一个 TransformStream,内部就是带 stream: true 的解码器,结束时会自动收尾,适合直接 pipeThrough 串起来。手动写的好处是控制更细,比如边解码边统计字节数。

GBK 编码的流能这样解码吗?

可以。浏览器的 TextDecoder 支持 GBK、Big5、Shift_JIS 等编码,写成 new TextDecoder('gbk') 即可,流式用法完全一样,GBK 的汉字是 2 个字节,同样会跨块。反过来 TextEncoder 只能输出 UTF-8,这是两者不对称的地方。

从一道题,走向一组知识

把知识连起来

数据处理

字节不是合法 UTF-8 时,TextDecoder 会报错还是出乱码?

流式解码最后一次 decode() 如果还剩不完整的字节,默认输出替换字符,开 fatal 才会抛错。

数据处理

TextEncoder 为什么只能输出 UTF-8,能指定成 GBK 吗?

编码方向看一个字符变成几个字节,就能理解解码时为什么会出现半个字符。

参考资料

  • JavaScript:数据处理参考资料

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

本题目录
  1. 先记住这个答案
  2. 一个字被切成两块会怎样
  3. 真实场景:流式读取 fetch 响应
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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