调试时常用的console.log()的同步和异步问题

最近帮助一个同窗在调试问题的时候,console.log()的输出真的让我诧异了一把,由于它居然会出现异步输出的状况,于是误导了咱们的判断,找错了方向,耽误了不少时间,因此这里记录一下遇到的这个问题,加深印象。javascript

问题现象:

正常输出:
imagejava

异常输出:
image浏览器

咱们能够发现,异常输出的时候,没展开的时候,显示的name值是Tom,点击箭头展开对象里的name则是Jack,并且,此时的输出值Jack执行语句,明显是在赋值语句obj.per.name = 'Jack'以前调用的,展开的时候,却变成了Jack,明显有些异常,是否是很神奇?我刚遇到这个问题的时候,也是懵逼状态的。app

为何会出现这个异常输出呢?异步

异常出现缘由分析

在分析以前,咱们得知道一点,JS中对象是引用类型,每次使用对象时,都只是使用了对象在堆中的引用。async

当咱们在使用obj.per.name = 'Jack'改变了对象的属性值时,它在堆中name的值也变成了'Jack',而当咱们不展开对象看的时候,console.log打印的是对象当时的快照,因此咱们看到的name属性值是没改变以前的'Tom',展开对象时,它实际上是从新去内存中读取对象的属性值,因此当咱们展开对象后看到的name属性值是Jack工具

浏览器或者能够说是开发者工具为何会有这样的表现?

这个问题在《你不知道的javascript中卷》第二部分异步和性能1.1节异步控制台部分有说起:性能

There is no specification or set of requirements around how the console.* methods work -- they are not officially part of JavaScript, but are instead added to JS by the hosting environment (see the Types & Grammar title of this book series).
So, different browsers and JS environments do as they please, which can sometimes lead to confusing behavior.
In particular, there are some browsers and some conditions that console.log(..) does not actually immediately output what it's given. The main reason this may happen is because I/O is a very slow and blocking part of many programs (not just JS). So, it may perform better (from the page/UI perspective) for a browser to handle console I/O asynchronously in the background, without you perhaps even knowing that occurred.

翻译:ui

并无什么规范或一组需求指定console.* 方法族如何工做——它们并非JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到JavaScript 中的。所以,不一样的浏览器和JavaScript 环境能够按照本身的意愿来实现,有时候这会引发混淆。
尤为要提出的是,在某些条件下,某些浏览器的console.log(..) 并不会把传入的内容当即输出。出现这种状况的主要缘由是,在许多程序(不仅是JavaScript)中,I/O 是很是低速的阻塞部分。因此,(从页面/UI 的角度来讲)浏览器在后台异步处理控制台I/O 可以提升性能,这时用户甚至可能根本意识不到其发生。

书中还了个例子:this

var a = {
    index: 1
};
// 而后
console.log( a ); // ??
// 再而后
a.index++;

咱们一般认为刚好在执行到console.log(..) 语句的时候会看到a 对象的快照,打印出相似于{ index: 1 } 这样的内容,而后在下一条语句a.index++ 执行时将其修改,这句的执行会严格在a 的输出以后。

多数状况下,前述代码在开发者工具的控制台中输出的对象表示与指望是一致的。可是,这段代码运行的时候,浏览器可能会认为须要把控制台I/O 延迟到后台,在这种状况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,所以会显示{ index: 2 }。

到底何时控制台I/O 会延迟,甚至是否可以被观察到,这都是游移不定的。

因此若是在调试的过程当中遇到对象在console.log(..) 语句以后被修改,可你却看到了意料以外的结果,要意识到这多是这种I/O 的异步化形成的。

书中建议:

若是遇到这种少见的状况,最好的选择是在JavaScript 调试器中使用断点,而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,好比经过JSON.stringify(..)。

结论

因而可知,console.log打印出来的内容并非必定百分百可信的内容。通常对于基本类型number、string、boolean、null、undefined的输出是可信的。但对于Object等引用类型来讲,则就会出现上述异常打印输出。

因此对于通常基本类型的调试,调试时使用console.log来输出内容时,不会存在坑。但调试对象时,最好仍是使用打断点(debugger)这样的方式来调试更好。

相关文章
相关标签/搜索