JavaScript
大 JSON 的处理方案
一、问题在哪
JSON.stringify(hugeObj) // 同步,阻塞主线程
JSON.parse(hugeString) // 同步,阻塞主线程
// 100MB 的 JSON → 主线程卡死几秒,页面无响应二、方案一:丢给 Worker
// 主线程
const worker = new Worker('json-worker.js')
worker.postMessage({ type: 'stringify', data: hugeObj })
worker.onmessage = (e) => {
console.log(e.data) // 序列化后的字符串
}// json-worker.js
self.onmessage = (e) => {
const { type, data } = e.data
if (type === 'stringify') {
self.postMessage(JSON.stringify(data))
} else if (type === 'parse') {
self.postMessage(JSON.parse(data))
}
}postMessage 传大对象本身需要结构化克隆,也有开销。如果是字符串,用 Transferable 传 ArrayBuffer 实现零拷贝:
// 主线程 → Worker 传大字符串
const encoder = new TextEncoder()
const buffer = encoder.encode(hugeJsonString).buffer
worker.postMessage(buffer, [buffer]) // 零拷贝转移
// worker.js
self.onmessage = (e) => {
const str = new TextDecoder().decode(e.data)
const obj = JSON.parse(str)
self.postMessage(obj)
}三、方案二:流式解析
浏览器端 — fetch ReadableStream
async function parseHugeJson(url) {
const response = await fetch(url)
const reader = response.body.getReader()
const decoder = new TextDecoder()
let jsonStr = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
jsonStr += decoder.decode(value, { stream: true })
}
// 读取过程不阻塞,但最后 parse 还是同步的
return JSON.parse(jsonStr)
}真正的流式解析需要 oboe.js,边解析边回调,不用等全部加载完:
oboe('/api/huge-data.json').node('users[*]', (user) => {
processUser(user)
return oboe.drop // 处理完立即释放内存
})Node 端 — stream-json
const { parser } = require('stream-json')
const { streamArray } = require('stream-json/streamers/StreamArray')
const fs = require('fs')
fs.createReadStream('huge.json')
.pipe(parser())
.pipe(streamArray())
.on('data', ({ value }) => processItem(value))
// 内存始终只保留当前那一条四、方案三:分片 + requestIdleCallback
function stringifyChunked(arr, chunkSize = 1000) {
return new Promise((resolve) => {
const chunks = []
let i = 0
function processChunk() {
const slice = arr.slice(i, i + chunkSize)
chunks.push(JSON.stringify(slice).slice(1, -1)) // 去掉 []
i += chunkSize
if (i < arr.length) {
requestIdleCallback(processChunk) // 浏览器空闲时处理下一块
} else {
resolve(`[${chunks.join(',')}]`)
}
}
processChunk()
})
}五、方案四:换二进制格式
JSON 对大数据天然不友好,考虑换格式从根本上解决:
// MessagePack — 比 JSON 小 30-50%,编解码更快
import { encode, decode } from '@msgpack/msgpack'
const packed = encode(hugeObj) // → Uint8Array
const obj = decode(packed)| 格式 | 体积 | 速度 | 可读性 |
|---|---|---|---|
| JSON | 大 | 慢 | ✅ 可读 |
| MessagePack | 小 30-50% | 快 2-3x | ❌ 二进制 |
| Protobuf | 最小 | 最快 | ❌ 需要 schema |
六、方案选择
| 场景 | 推荐方案 |
|---|---|
| 前端处理几十 MB JSON | Worker + Transferable |
| 前端展示超大列表 | 流式解析(oboe.js)+ 虚拟列表 |
| Node 处理 GB 级文件 | stream-json 流式处理 |
| 数据不算大但不想阻塞 UI | requestIdleCallback 分片 |
| 前后端都能改 | 换 MessagePack / Protobuf |
七、一句话总结
大 JSON 的核心问题是
parse/stringify同步阻塞。优先用 Worker 移出主线程;超大文件用流式解析边读边处理;阻塞不严重时用requestIdleCallback分片;前后端都能改则换二进制格式从根本上降低体积和解析耗时。