你能读多大的 JSON 文件?我试了 500MB,发现 JSON.parse 会直接让页面崩溃

你能读多大的 JSON 文件?我试了 500MB,发现 JSON.parse 会直接让页面崩溃

导语

做数据看板或日志查看器时,很多人第一个想到的就是:把后端给的 JSON 一整个 JSON.parse 扔进内存。可如果这个文件是 100MB、300MB、甚至 500MB,页面很可能直接卡死、白屏,甚至标签页崩溃。这篇文章用一次真实试错,说清楚为什么大 JSON 会把页面卡死,以及三个你当天就能落地的解法。

正文

很多人以为“JSON 只是一段文本”,所以 JSON.parse 应该很轻。实际上,JSON.parse 在解析时会做以下几件事:

  • 递归把字符串展开成完整对象
  • 分配大量临时对象和数组
  • 触发主线程长任务,浏览器输入/动画全部卡住
  • 内存峰值翻倍:文本本身 + 解析后的对象图

我拿 100MB、300MB、500MB 三个 JSON 做了一次简单测试,结果很直接:

  • 100MB:主线程卡 400~700ms
  • 300MB:标签页开始掉帧,部分浏览器直接假死
  • 500MB:Chrome 直接弹“页面无响应”,最终崩溃

说白了,问题不是 JSON 本身,而是 JSON.parse 是一次性、同步、阻塞主线程的操作。

方案一:先问自己,真的需要全部加载吗?

很多时候,大 JSON 的瓶颈是“我要的只有 20 条,却拿回了 500MB”。优先从数据源头解决:

  • 服务端分页、按需字段过滤
  • 支持范围请求,只取当前页
  • 换更紧凑的格式,如 protobuf / msgpack / flatbuffers

这是成本最低的方案,但很多时候改不了后端。那就看前端怎么处理。

方案二:流式读取,不要一次性读全文

浏览器其实已经有现成的流 API。这里最实用的是 fetch + ReadableStream,因为不需要任何库,所有现代浏览器都支持。

以日志查看器为例,如果你想边下边看,可以这样:

async function streamJson(url) {  const response = await fetch(url);  if (!response.ok) throw new Error(`请求失败:${response.status}`);  const reader = response.body.getReader();  const decoder = new TextDecoder();  let buffer = '';  let depth = 0;  let inString = false;  let escapeNext = false;  while (true) {    const { done, value } = await reader.read();    if (done) break;    buffer += decoder.decode(value, { stream: true });    for (let i = 0; i < buffer.length; i++) {      const ch = buffer[i];      if (inString) {        if (escapeNext) { escapeNext = false; continue; }        if (ch === '\') { escapeNext = true; continue; }        if (ch === '"') inString = false;        continue;      }      if (ch === '"') { inString = true; continue; }      if (ch === '{' || ch === '[') depth++;      else if (ch === '}' || ch === ']') depth--;      if (depth === 0 && (ch === '}' || ch === ']')) {        // 已拿到可解析的 JSON 片段      }    }    buffer = buffer.slice(Math.max(0, buffer.length - 200));  }}

这段代码的核心不是完美解析,而是告诉你一个思路:流式处理大 JSON 的关键,是把“全文解析”拆成“边读边剪”

方案三:用更适合大文件的解析策略

如果你必须在前端拿到完整结果,至少也分三步走:

  • 先测内存:解析前先判断文件大小
  • 小文件直接 JSON.parse
  • 大文件走 Web Worker,避免阻塞 UI
async function safeParseJson(text) {  const sizeMB = new Blob([text]).size / 1024 / 1024;  if (sizeMB > 50) {    const blob = new Blob([`      self.onmessage = (e) => {        try {          const data = JSON.parse(e.data);          self.postMessage({ ok: true, data });        } catch (err) {          self.postMessage({ ok: false, error: err.message });        }      };    `], { type: 'application/javascript' });    const worker = new Worker(URL.createObjectURL(blob));    const result = await new Promise((resolve) => {      worker.onmessage = (e) => resolve(e.data);      worker.postMessage(text);    });    worker.terminate();    if (result.ok) return result.data;    throw new Error(result.error);  }  return JSON.parse(text);}

这个函数很适合包装成一个工具函数:小文本直接解析,大文本自动进 Worker。

结论:不要默认“前端能直接吃下任意 JSON”

这次 500MB 测试最大的收获不是代码技巧,而是认知:

  • 能用流,就不要全量读
  • 能用服务端过滤,就不要把大包甩给前端
  • 能分页,就不要一次全给

如果你正好在做日志查看器、数据导出、报表系统,或者任何“后端一甩就是几十 MB”的场景,先把这三点想清楚,再决定要不要 JSON.parse

收尾

下次遇到“页面一加载就崩”的数据场景,先问三个问题:

1. 我真的需要全量数据吗?

2. 服务端能不能分片/过滤?

3. 前端是不是该用流或 Worker?

把这三个问题想清楚,比直接换一个解析库更重要。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单