有时候调试代码,发现所看的结果与期望的有差异,误导了我们的判断,找错了方向,耽误了很多时间,console.log()
的输出竟然会出现异步输出的情况,因而所以这里记录一下遇到的这个问题,加深印象。
chrome 浏览器测试
可以看出,当 console.log(obj.per)
看到的还是未修改的 vv
,一旦展开却变成了 呱呱
,为什么会有这个异常输出
原因:
这里不得不提到 js 的对象是引用类型,每次使用对象时候,都只是引用了对象在堆中的引用,当修改了 obj.per.name 时候,也修改了堆中引用的 name,当 console.log(obj.per)
打印的是对象当时的快照信息,当展开对象时候,会去内存读对象的属性值。
为什么开发者工具有这个表现?
《你不知道的javascript中卷》第二部分异步和性能1.1节异步控制台部分有提及:
翻译:并没有什么规范或一组需求指定console.* 方法族如何工作——它们并不是JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到JavaScript 中的。因此,不同的浏览器和JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。
尤其要提出的是,在某些条件下,某些浏览器的console.log(..) 并不会把传入的内容立即输出。出现这种情况的主要原因是,在许多程序(不只是JavaScript)中,I/O 是非常低速的阻塞部分。所以,(从页面/UI 的角度来说)浏览器在后台异步处理控制台I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。
书中还举了一个例子
var a = { index: 1};// 然后console.log( a ); // ??// 再然后a.index++;
类似的,当执行输出 a 时,会显示 a 的快照,而 a.index ++ 的确严格执行在 console.log 之后,但当你展开 对象 a 时候,会去内存中去读取 a.index 值,浏览器可能会认为需要把控制台I/O 延迟到后台,这时候可能修改成了 2。
到底什么时候控制台I/O 会延迟,甚至是否能够被观察到,这都是游移不定的。
所以如果在调试的过程中遇到对象在console.log(..) 语句之后被修改,可你却看到了意料之外的结果,要意识到这可能是这种I/O 的异步化造成的。
书中建议:
如果遇到这种少见的情况,最好的选择是在JavaScript 调试器中使用断点,而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过JSON.stringify(..)。
结论:
由此可见,console.log打印出来的内容并不是一定百分百可信的内容。一般对于基本类型number、string、boolean、null、undefined
的输出是可信的。但对于Object
等引用类型来说,则就会出现上述异常打印输出。所以对于一般基本类型的调试,调试时使用console.log来输出内容时,不会存在坑。但调试对象时,最好还是使用打断点(debugger
)这样的方式来调试更好。