調試時常用的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)這樣的方式來調試更好。

相關文章
相關標籤/搜索