有時候調試代碼,發現所看的結果與期望的有差異,誤導了我們的判斷,找錯了方向,耽誤了很多時間,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
)這樣的方式來調試更好。