JS对象转字符串:JSON.stringify用法与参数详解
深入详解 JavaScript 中 JSON.stringify 的完整用法,包括 value、replacer、space 三大参数的机制、差异对比、特殊数据类型转换边界与常见陷阱处理。
在 JavaScript 中,将对象转化为可持久化存储或网络传输的字符串,最直接且标准的方法是使用内置的 JSON.stringify()。它不仅能够将常规对象与数组转换为标准 JSON 字符串,还允许通过额外的参数实现属性筛选、脱敏替换、结构定制及缩进排版。
一、基础语法与调用形式
在日常开发中,前端向后端提交数据、向 localStorage 写入缓存,或者记录运行日志时,都必须将内存中的对象转化为符合规范的文本。最基础的调用只需传入一个参数:
const user = {
id: 101,
name: "张三",
isAdmin: false
};
const jsonStr = JSON.stringify(user);
console.log(jsonStr);
console.log(typeof jsonStr);
string
尽管单参数调用最为普遍,但 JSON.stringify 的完整方法签名包含三个参数:
JSON.stringify(value[, replacer[, space]])
掌握后面两个可选参数,能让我们直接完成按需取字段、敏感数据掩码以及格式化排版等需求,避免在序列化前手动做浅拷贝或多余的对象重构。
二、参数一:value 与自定义 toJSON 机制
value 是必填项,代表将被序列化的目标值。除了普通对象和数组外,它还可以接收基本类型(字符串、数字、布尔值等)。
在处理 value 时,引擎首先会检查该对象或其原型链上是否声明了 toJSON() 方法。若存在,该方法会被立即执行,并将方法执行后的返回值作为本次序列化的实际输入,而不是直接序列化原对象。
const book = {
title: "JavaScript 高级程序设计",
price: 99,
secretStock: 450,
toJSON() {
return {
title: this.title,
price: `¥${this.price}`
};
}
};
console.log(JSON.stringify(book));
JavaScript 内置的 Date 对象正是利用了这一机制:Date.prototype.toJSON() 内部直接委托给 toISOString(),因此 JSON.stringify(new Date()) 得到的是符合 ISO 8601 标准的日期字符串,而非包含内部时间戳的普通对象。
三、参数二:replacer 的两种形态与关键差异
第二个参数 replacer 是控制序列化细节的核心。它可以是一个过滤数组,也可以是一个转换函数。两种形态在设计目标、控制粒度和执行机制上存在明显差异。
1. 作为数组:声明式白名单
当 replacer 是一个字符串或数字组成的数组时,只有包含在数组内的属性名才会被序列化,其余属性全部过滤:
const student = {
name: "小李",
age: 18,
grade: "高三",
token: "secret_xyz_9988"
};
// 仅保留 name 与 age
const filtered = JSON.stringify(student, ["name", "age"]);
console.log(filtered);
[student]),白名单不会限制数组外层索引,但仍会对数组内包含的对象属性生效。
2. 作为函数:动态计算与精准过滤
当 replacer 为函数时,函数接收两个参数:key 和 value。引擎会采用深度优先方式递归遍历每一个键值对。
初次调用非常特殊:第一次进入 replacer 时,key 是一个空字符串 "",而 value 是待序列化的整个顶层对象。随后才会依次递归每个子键。
const account = {
id: 1001,
username: "hunter",
passwordHash: "e10adc3949ba59abbe56e057f20f883e",
balance: 8500.5,
isVip: true
};
const safeJson = JSON.stringify(account, (key, value) => {
// 顶层对象时 key 为 "",必须直接返回该 value
if (key === "passwordHash") {
return undefined; // 返回 undefined 会剔除该属性
}
if (key === "balance") {
return Math.floor(value); // 将金额取整
}
return value; // 其余值原样返回
});
console.log(safeJson);
如果函数返回了 undefined,该属性会被完全剔除;如果返回的是其他数据类型,序列化过程将使用该返回值。这为敏感信息抹除和特定数据转化提供了极大的自由度。
四、参数三:space 与格式化排版
第三个参数 space 用于控制结果字符串的排版空白,改善人眼阅读体验(美化输出)。
- 传入数字:表示每一级缩进的空格数。取值范围为
1到10。若小于 1,则视为无缩进;若大于 10,会被自动截断为 10。 - 传入字符串:该字符串会被直接作为缩进前缀(例如使用制表符
" "或破折号"--"),最长取前 10 个字符。 - 不传或为 null:保持紧凑输出,无任何换行和多余空格,适合网络传输以节约带宽。
const config = { host: "localhost", port: 8080 };
// 缩进 2 个空格
console.log(JSON.stringify(config, null, 2));
// 使用制表符缩进
console.log(JSON.stringify(config, null, " "));
"host": "localhost",
"port": 8080
}
五、特殊类型转换规则与边界限制
并非所有 JavaScript 类型都能直接映射为 JSON 标准类型。在遇到函数、未定义值、特殊数值或非标准容器时,JSON.stringify 会根据其所处的位置采取截然不同的静默处理策略。
| 数据类型 | 作为对象属性 | 作为数组元素 | 单独序列化 |
|---|---|---|---|
undefined |
属性直接被忽略(丢失) | 转换为 null |
返回 undefined |
Function |
属性直接被忽略(丢失) | 转换为 null |
返回 undefined |
Symbol |
属性直接被忽略(丢失) | 转换为 null |
返回 undefined |
NaN / Infinity |
转换为 null |
转换为 null |
返回 "null" |
BigInt |
抛出 TypeError |
抛出 TypeError |
抛出 TypeError |
| 循环引用对象 | 抛出 TypeError |
抛出 TypeError |
抛出 TypeError |
1. 为什么 BigInt 会抛出 TypeError?
JSON 规范中的数字采用双精度浮点数标准,而 BigInt 用于表示任意精度的整数。如果无序隐式转换为普通数字,将导致严重的精度截断问题。因此,JavaScript 引擎选择在遇到 BigInt 时直接抛出 TypeError: Do not know how to serialize a BigInt。
解决办法:在 replacer 中将其显式转换为字符串,或者扩展 BigInt.prototype.toJSON:
const data = { count: 9007199254740993n };
const result = JSON.stringify(data, (key, value) => {
return typeof value === "bigint" ? value.toString() : value;
});
console.log(result);
2. 如何安全处理循环引用?
当对象内部的某个属性直接或间接引用了该对象本身时,序列化过程将陷入无限递归,导致抛出 TypeError: Converting circular structure to JSON:
const nodeA = { name: "节点 A" };
const nodeB = { name: "节点 B" };
nodeA.sibling = nodeB;
nodeB.sibling = nodeA; // 构造相互引用
// 直接执行会报错:
// JSON.stringify(nodeA);
解决方案:利用 WeakSet 记录已经访问过的对象,若重复出现则返回 undefined 打破循环:
function stringifySafely(obj, space = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]"; // 或者返回 undefined 忽略
}
seen.add(value);
}
return value;
}, space);
}
console.log(stringifySafely(nodeA));
"name": "节点 A",
"sibling": {
"name": "节点 B",
"sibling": "[Circular]"
}
}
六、实战场景决策与知识总结
为了让不同开发场景下的参数选型更加清晰,下图梳理了从基础序列化到复杂数据脱敏的决策路径:
综上所述,JSON.stringify() 绝非仅仅是一个单一功能的字符串转换器,而是一个兼具数据过滤、类型拦截与文本排版能力的序列化管道。理解 toJSON 的执行优先权、熟练掌握 replacer 的函数与数组形态差异,并牢记 undefined、BigInt 以及循环引用的边界规则,能够帮助我们在日常开发中写出更加健壮、高效的数据序列化代码。
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
相关标签:








