跳到主要内容

说谎的 console.log

· 阅读需 4 分钟
youniaogu

说谎的 console.log

1、前言

console.log,前端在调试时用得最多的小伙伴,但你可能不知道,它有时候也会说谎

之所以要说这个,原因是最近修 🐛 时,无意中发现console.log断点调试输出与非断点输出不一致

2、为什么 console.log 输出会不一致?

首先,我们要明确一个观念,console.log是同步的还是异步的?

理论上,console.log是同步的,他们实时输出数据,但在输出引用类型的数据,会有不同的行为

可以看到,引用类型数据如果存在嵌套时(图中红线),log 不会继续输出里面的数据,而是会省略

而且引用类型还可以展开,用来查看里面嵌套的数据,这是我们平时调试常用的动作

console.log输出不一致的原因,就在于展开这个动作

之前console.log输出的数据并不完整,只能说是一种快照。而在展开时,则会往堆内存里拿最新的数据,这中间如果有对数据进行修改的话,就会出现异步的假象

举个 🌰

下面的代码,立即展开和三秒后展开会有不同的结果

const obj = { name: "start" };

console.log(obj);

setTimeout(() => {
obj.name = "after 3 seconds";
}, 3000);

回到最开始的问题,为什么断点和非断点调试,console.log输出会不一致

答案很明了,代码里存在对输出数据的修改,导致两次调试结果不一致。

而就结果来看,断点调试的输出才是正确的,也是我们想要的(都断点了,谁还看 log 呀)。

3、为什么会这么设计?

主要原因在于引用类型会存在循环引用的情况,一次性打印出来的话就会死循环,而如果缓存数据,则会占用资源

注意:以上测试与结论都基于 chrome 浏览器,不同浏览器实现可能不一样

4、总结

console.log在打印引用类型时,会立马输出当前的快照,并会省略嵌套的引用类型数据。而后面展开数据时,会往堆里拿取最新数据,同时也会缓存起来(后续开关不会更新数据)

并不只是console.log有这个特性,只要是涉及展开这个动作,就会存在

了解这个特性可以让我们 debug 少走弯路,但有一点要明白,最好的 debug 方法还得是断点

除此之外,良好的代码习惯也是必不可少的(前言里的问题是因为在 react 里直接修改 state,然后使用 forceUpdate 进行 render 导致,我真是佩服写出这个的老哥)