console.log 的“动态表演”
"对象的输出,咋还动态更新了?这还是我认识的那个老实巴交的 console.log 吗?"
提起console.log
对于切图仔的我来说,那简直是熟的不能在熟了。
1. 故事的开始
今天在调试项目的时候遇到类似的代码。
const obj = { a: 1, b: 2 };
console.log(obj); // 看似 { a: 1, b: 2 }
obj.c = 3; // 给对象加了个字段
点开控制台,惊讶地发现:
{ a: 1, b: 2, c: 3 }
等等,console.log
输出的对象不是应该固定下来了吗?c
字段怎么“凭空出现”了?我内心窃喜,仿佛又发现了一个诡异的“Bug”。这时,传来了冲哥熟悉的声音:“走,冒一根?”我说:“冲哥别急,我又发现一个Bug!”迫不及待地拿起鼠标,在控制台迅速输入了那段代码:“看,这里我给对象添加了一个新属性 c,然后刷新控制台,输出直接自动更新了!”冲哥邪魅一笑,说道:“该去买本书看看了。”
2. 你不知道的console.log
其实,这并不是 Bug,而是浏览器中 console.log
的**“懒惰行为”**。当你在控制台打印对象时,console.log
输出的并不是一个“快照”,而是对对象的引用。
也就是说,
- 你打印的时候是什么样,它就展示什么样。
- 后续对象有变化,引用也会变。
这就是为什么在 obj.c = 3
后,控制台中的对象内容也发生了更新。
3. 为什么浏览器要这么设计?
浏览器这样做的初衷可能是出于以下考虑:
- 节省资源:直接引用对象比深拷贝对象更高效,尤其是大对象。
- 方便调试:动态更新能让开发者实时观察对象的变化。
虽然有这些优点,但如果你不了解这背后的机制,非常容易“翻车”。
4. Node.js 中的表现
你可能会问:在 Node.js 中会这样吗?答案是否定的!
在 Node.js 中,console.log
是基于 util.inspect
实现的,它输出的对象是当前状态的“快照”,而不是引用。这就意味着,即使你修改了对象,之前的输出也不会变。
看下面这个例子:
const obj = { a: 1, b: 2 };
console.log(obj); // 输出 { a: 1, b: 2 }
obj.c = 3;
console.log(obj); // 输出 { a: 1, b: 2, c: 3 }
第一次输出的内容不会受到 obj.c = 3
的影响。
5. 如何规避这个坑?
如果你想避免浏览器的这个“懒惰输出”,有以下几种方法:
方法 1:使用 JSON 序列化
console.log(JSON.parse(JSON.stringify(obj)));
通过 JSON.stringify
和 JSON.parse
,你可以输出对象的一个快照,后续修改不会影响输出。
方法 2:使用 console.dir
(部分场景有效)
某些浏览器支持 console.dir
,它可以输出对象的更详细信息,不过它在引用处理上和 console.log
差异不大。
方法 3:直接打印对象的属性
console.log(obj.a, obj.b);
只打印需要的字段,可以避免引用更新的问题。