摘要:最近帮助一个同学在调试问题的时候,的输出真的让我诧异了一把,因为它竟然会出现异步输出的情况,因而误导了我们的判断,找错了方向,耽误了很多时间,所以这里记录一下遇到的这个问题,加深印象。但调试对象时,最好还是使用打断点这样的方式来调试更好。
最近帮助一个同学在调试问题的时候,console.log()的输出真的让我诧异了一把,因为它竟然会出现异步输出的情况,因而误导了我们的判断,找错了方向,耽误了很多时间,所以这里记录一下遇到的这个问题,加深印象。
问题现象:正常输出:
异常输出:
我们可以发现,异常输出的时候,没展开的时候,显示的name值是Tom,点击箭头展开对象里的name则是Jack,而且,此时的输出值Jack执行语句,明显是在赋值语句obj.per.name = "Jack"之前调用的,展开的时候,却变成了Jack,明显有些异常,是不是很神奇?我刚遇到这个问题的时候,也是懵逼状态的。
为什么会出现这个异常输出呢?
异常出现原因分析在分析之前,我们得知道一点,JS中对象是引用类型,每次使用对象时,都只是使用了对象在堆中的引用。
当我们在使用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.
翻译:
并没有什么规范或一组需求指定console.* 方法族如何工作——它们并不是JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到JavaScript 中的。因此,不同的浏览器和JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。
尤其要提出的是,在某些条件下,某些浏览器的console.log(..) 并不会把传入的内容立即输出。出现这种情况的主要原因是,在许多程序(不只是JavaScript)中,I/O 是非常低速的阻塞部分。所以,(从页面/UI 的角度来说)浏览器在后台异步处理控制台I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。
书中还了个例子:
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)这样的方式来调试更好。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95327.html
摘要:事件循环从回调队列中获取并将其推入调用堆栈。执行从调用堆栈中移除从调用堆栈中移除快速回顾值得注意的是,指定了事件循环应该如何工作,这意味着在技术上它属于引擎的职责范围,不再仅仅扮演宿主环境的角色。 此篇是 JavaScript是如何工作的第四篇,其它三篇可以看这里: JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript是如何工作的:深入V8引擎&编写...
摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...
摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...
摘要:控制台将显示回调地狱通常,回调只能由一个异步函数调用。更多资源使更友好规范使用异步函数简化异步编码旅程异步编程是一项在中无法避免的挑战。 JavaScript经常声称是_异步_。那是什么意思?它如何影响发展?近年来这种方法有何变化? 请思考以下代码: result1 = doSomething1(); result2 = doSomething2(result1); 大多数语言都处理每...
摘要:所以增加了异步函数,提高了代码可读性,对不太熟悉的人而言,帮助就更大了。因为异步函数去掉了所有回调。这就是此代码有效的原因它和以下一样代码更易读正如上面示例所见,与回调和代码相比,异步函数代码看起来非常简单。 这篇文章详细讲解了JavaScript中的异步函数。 JavaScript中的异步代码在很短的时间内从回调发展为Promise,再到ES2017的异步函数,现在我们可以像编写同步...
阅读 1907·2021-09-23 11:21
阅读 1693·2019-08-29 17:27
阅读 1053·2019-08-29 17:03
阅读 719·2019-08-29 15:07
阅读 1915·2019-08-29 11:13
阅读 2374·2019-08-26 12:14
阅读 904·2019-08-26 11:52
阅读 1729·2019-08-23 17:09