Skip to content

console.log 的“动态表演”

"对象的输出,咋还动态更新了?这还是我认识的那个老实巴交的 console.log 吗?"

提起console.log对于切图仔的我来说,那简直是熟的不能在熟了。


1. 故事的开始

今天在调试项目的时候遇到类似的代码。

javascript
const obj = { a: 1, b: 2 };
console.log(obj); // 看似 { a: 1, b: 2 }
obj.c = 3;        // 给对象加了个字段

点开控制台,惊讶地发现:

javascript
{ a: 1, b: 2, c: 3 }

等等,console.log 输出的对象不是应该固定下来了吗?c 字段怎么“凭空出现”了?我内心窃喜,仿佛又发现了一个诡异的“Bug”。这时,传来了冲哥熟悉的声音:“走,冒一根?”我说:“冲哥别急,我又发现一个Bug!”迫不及待地拿起鼠标,在控制台迅速输入了那段代码:“看,这里我给对象添加了一个新属性 c,然后刷新控制台,输出直接自动更新了!”冲哥邪魅一笑,说道:“该去买本书看看了。”


2. 你不知道的console.log

其实,这并不是 Bug,而是浏览器中 console.log 的**“懒惰行为”**。当你在控制台打印对象时,console.log 输出的并不是一个“快照”,而是对对象的引用。

也就是说,

  • 你打印的时候是什么样,它就展示什么样。
  • 后续对象有变化,引用也会变。

这就是为什么在 obj.c = 3 后,控制台中的对象内容也发生了更新。


3. 为什么浏览器要这么设计?

浏览器这样做的初衷可能是出于以下考虑:

  1. 节省资源:直接引用对象比深拷贝对象更高效,尤其是大对象。
  2. 方便调试:动态更新能让开发者实时观察对象的变化。

虽然有这些优点,但如果你不了解这背后的机制,非常容易“翻车”。


4. Node.js 中的表现

你可能会问:在 Node.js 中会这样吗?答案是否定的!

在 Node.js 中,console.log 是基于 util.inspect 实现的,它输出的对象是当前状态的“快照”,而不是引用。这就意味着,即使你修改了对象,之前的输出也不会变。

看下面这个例子:

javascript
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 序列化

javascript
console.log(JSON.parse(JSON.stringify(obj)));

通过 JSON.stringifyJSON.parse,你可以输出对象的一个快照,后续修改不会影响输出。

方法 2:使用 console.dir(部分场景有效)

某些浏览器支持 console.dir,它可以输出对象的更详细信息,不过它在引用处理上和 console.log 差异不大。

方法 3:直接打印对象的属性

javascript
console.log(obj.a, obj.b);

只打印需要的字段,可以避免引用更新的问题。