一、 语法边界:JSON 并不是 JavaScript 的子集
这是一个常见的误区。虽然 JSON 源于 JS,但它的规范(RFC 8259)比 JS 严格且局限得多。
1. 那些被"吞掉"的类型
在执行 JSON.stringify(obj) 时,JS 引擎会进行一套复杂的类型转换,而这些转换往往是非对称的:
-
undefined、函数、Symbol:
- 作为对象属性时:会被直接忽略(Key 都会消失)。
- 作为数组元素 时:会被转化为
null。 - 独立值 时:返回
undefined。
-
不可枚举属性:默认会被完全忽略。
-
BigInt :会直接抛出
TypeError,因为 JSON 规范中没有对应的大数表示协议。
2. 数值的精度丢失
JSON 的数值遵循 IEEE 754 双精度浮点数。如果你在处理前端监控中的高精纳秒级时间戳,直接序列化可能会导致精度被截断。
二、 序列化的高级操纵:Replacer 与 toJSON 的深度应用
当你需要处理复杂的业务对象(比如含有循环引用或敏感数据)时,基础的 JSON.stringify 就不够用了。
1. toJSON:对象的自白
如果一个对象拥有 toJSON 方法,序列化时会优先调用它。这在处理复杂类实例(Class)时非常有用:
JavaScript
kotlin
class User {
constructor(name, pwd) { this.name = name; this.pwd = pwd; }
toJSON() { return { name: this.name }; } // 自动屏蔽敏感字段
}
2. Replacer 过滤器:解决循环引用
面对嵌套极深的监控数据,循环引用会导致进程崩溃。我们可以利用 Replacer 的第二个参数(函数或数组)来进行"外科手术":
JavaScript
javascript
const seen = new WeakSet();
const safeJson = JSON.stringify(data, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]"; // 标记循环引用而非报错
seen.add(value);
}
return value;
});
三、 性能深水区:V8 引擎是如何"吃"掉 JSON 的?
在 Node.js 服务端,大规模的 JSON 处理往往是 CPU 的头号杀手。
1. 为什么 JSON.parse 比 JS 字面量快?
这是一个反直觉的结论:解析一段字符串 JSON.parse('{"a":1}') 通常比 JS 引擎解析代码 {a:1} 快。
- 原因 :JS 解析器需要进行复杂的词法和语法分析(考虑到变量提升、作用域等),而 JSON 解析器是单向、无状态的。
- 优化建议 :对于大型静态配置,直接以字符串形式存放并用
JSON.parse载入,能有效缩短代码冷启动的解析时间(Parse Time)。
2. 阻塞与内存的"双重打击"
- 同步阻塞 :
JSON.stringify在处理 10MB 以上的对象时,会阻塞 Event Loop 几十毫秒。在高并发环境下,这足以导致后续请求全部超时。 - 内存膨胀 :序列化时,V8 会先生成一个完整的巨大字符串放入堆内存中。如果你的对象接近 1GB,序列化过程可能会瞬间触发 OOM (Out of Memory) 。
3. 安全陷阱:JSON 劫持与注入
__proto__注入 :不安全的JSON.parse(特别是在某些旧库中)可能被恶意构造的字符串攻击,通过原型链污染篡改全局逻辑。