Chrome/Edge v110+ 禁止非用户手势触发的 data: URL 自动下载,Firefox 行为不一致;正确做法是 JSON → Blob → URL.createObjectURL → click,需指定 type、及时 revoke、确保用户手势触发。

能直接导出,但必须用 URL.createObjectURL 中转,不能靠 fetch 或 XMLHttpRequest 直接触发下载。
为什么不能直接用 data: URL 触发下载
Chrome 和 Edge 从 v110+ 开始限制了 data: URL 的自动下载行为(尤其是非用户手势触发时),即使写成 location.href = 'data:application/json,...' 也会静默失败或被拦截。Firefox 虽稍宽松,但行为不一致,不可靠。
正确路径是:JS 生成 JSON → 构造 Blob → 用 URL.createObjectURL 创建临时对象 URL → 创建 标签并调用 .click()。
-
Blob类型必须显式指定type: 'application/json',否则部分浏览器可能保存为.txt - 对象 URL 必须在下载完成后手动释放(
URL.revokeObjectURL),否则内存泄漏 - 触发
.click()必须发生在用户事件回调内(如button.onclick),否则 Safari 会拒绝
Blob 构造时的编码和换行处理
JSON 字符串本身是 UTF-8,但若含中文或特殊符号,直接传入 new Blob([jsonString]) 可能因 JS 字符串内部编码与 Blob 默认编码不一致导致乱码。稳妥做法是显式转为 Uint8Array 或确保 Blob 构造参数为 string + 正确 type。
- 推荐写法:
new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })——JSON.stringify输出已是合法 UTF-8 字符串 - 避免:
new Blob([new TextEncoder().encode(jsonString)]),多此一举且易漏掉type - 如果后端要求无空格/无换行(如 minified),把
JSON.stringify(data)的第二个、第三个参数去掉即可
完整可运行示例(含错误防护)
function downloadJSON(data, filename = 'data.json') {
try {
const jsonStr = JSON.stringify(data, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); // 必须立即释放
} catch (err) {
console.error('JSON 导出失败:', err);
}
}
// 使用示例:
downloadJSON({ name: '张三', scores: [89, 92, 78] }, 'report.json');
注意:a.download 属性在 Firefox 中对跨域 URL 无效,但此处是 blob: 协议,完全兼容。Safari 对 download 属性支持良好,但要求点击必须由用户真实触发——别在 setTimeout 或异步回调里调用 downloadJSON。
最常被忽略的是 URL.revokeObjectURL 的调用时机和位置:它必须在 a.click() 之后、且不能放在异步回调里(比如 a.onload 不可靠),因为下载是异步发起但对象 URL 应立即释放。











