JSON.stringify(NaN)返回"null"是因为NaN不是合法JSON值,序列化时被规范强制转为null;深拷贝需用replacer/reviver标记还原、structuredClone或lodash.cloneDeep。

JavaScript 中使用 JSON.parse(JSON.stringify(obj)) 做深拷贝时,NaN 会被静默转为 null,这是 JSON 标准本身决定的——NaN 不是合法的 JSON 值,序列化阶段就被丢弃并替换为 null。要保留 NaN,不能依赖原生 JSON 方法,需自定义序列化/反序列化逻辑。
为什么 JSON.stringify(NaN) 得到 "null"
JSON.stringify 对非合法 JSON 值有明确处理规则:undefined、函数、Symbol 被忽略(对象中)或转为 null(数组中),而 NaN 和 Infinity 统一转为 null。这不是 bug,而是规范行为。
-
JSON.stringify({x: NaN})→"{"x":null}" -
JSON.stringify([NaN, 1])→"[null,1]" - 反序列化后,
null无法还原为NaN,信息已丢失
方案一:使用 replacer + reviver 手动标记 NaN
在序列化时用特殊字符串(如 "__NaN__")代替 NaN,反序列化时再还原。需确保该标记不会与业务数据冲突。
function deepCloneWithNaN(obj) {
const json = JSON.stringify(obj, (key, value) => {
return Number.isNaN(value) ? "__NaN__" : value;
});
return JSON.parse(json, (key, value) => {
return value === "__NaN__" ? NaN : value;
});
}
- 支持嵌套对象、数组中的
NaN - 不处理
Infinity,如需一并支持,可扩展为"__Infinity__"/"__-Infinity__" - 注意:若原始数据中恰好有字符串
"__NaN__",会误判,建议加前缀或使用 Symbol(但 Symbol 无法被 JSON 序列化)
方案二:用 structuredClone(现代浏览器 & Node.js 18.7+)
structuredClone 是原生深拷贝 API,能正确保留 NaN、Infinity、BigInt、Date、RegExp 等类型,且不走 JSON 路径。
立即学习“Java免费学习笔记(深入)”;
const cloned = structuredClone(original);- 完美支持
NaN:structuredClone({x: NaN}).x !== null && isNaN(structuredClone({x: NaN}).x)为true - 限制:不支持函数、undefined、循环引用;部分环境(如旧版 Node.js 或 IE)不可用
方案三:第三方库(如 lodash.cloneDeep)
lodash 的 cloneDeep 内部不依赖 JSON,而是递归遍历并按类型分别处理,天然保留 NaN。
const cloned = _.cloneDeep({x: NaN}); // cloned.x 仍是 NaN- 兼容性好,支持函数、正则、Map/Set 等复杂类型
- 如果项目已引入 lodash,这是最省心的选择;否则仅为此功能引入整包略重
不复杂但容易忽略:只要不是强制要求用 JSON.stringify,就别硬扛。优先选 structuredClone(新环境),次选 lodash.cloneDeep(兼容性优先),实在受限再用 replacer/reviver 自定义 JSON 方案。


















