有時候調(diào)試代碼,發(fā)現(xiàn)所看的結(jié)果與期望的有差異,誤導了我們的判斷,找錯了方向,耽誤了很多時間,console.log()的輸出竟然會出現(xiàn)異步輸出的情況,因而所以這里記錄一下遇到的這個問題,加深印象。
chrome 瀏覽器測試
可以看出,當 console.log(obj.per) 看到的還是未修改的 vv,一旦展開卻變成了 呱呱,為什么會有這個異常輸出
原因:
這里不得不提到 js 的對象是引用類型,每次使用對象時候,都只是引用了對象在堆中的引用,當修改了 obj.per.name 時候,也修改了堆中引用的 name,當 console.log(obj.per)
打印的是對象當時的快照信息,當展開對象時候,會去內(nèi)存讀對象的屬性值。
為什么開發(fā)者工具有這個表現(xiàn)?
《你不知道的javascript中卷》第二部分異步和性能1.1節(jié)異步控制臺部分有提及:
翻譯:并沒有什么規(guī)范或一組需求指定console.* 方法族如何工作——它們并不是JavaScript 正式的一部分,而是由宿主環(huán)境(請參考本書的“類型和語法”部分)添加到JavaScript 中的。因此,不同的瀏覽器和JavaScript 環(huán)境可以按照自己的意愿來實現(xiàn),有時候這會引起混淆。
尤其要提出的是,在某些條件下,某些瀏覽器的console.log(..) 并不會把傳入的內(nèi)容立即輸出。出現(xiàn)這種情況的主要原因是,在許多程序(不只是JavaScript)中,I/O 是非常低速的阻塞部分。所以,(從頁面/UI 的角度來說)瀏覽器在后臺異步處理控制臺I/O 能夠提高性能,這時用戶甚至可能根本意識不到其發(fā)生。
書中還舉了一個例子
var a = { index: 1};// 然后console.log( a ); // ??// 再然后a.index++;
類似的,當執(zhí)行輸出 a 時,會顯示 a 的快照,而 a.index ++ 的確嚴格執(zhí)行在 console.log 之后,但當你展開 對象 a 時候,會去內(nèi)存中去讀取 a.index 值,瀏覽器可能會認為需要把控制臺I/O 延遲到后臺,這時候可能修改成了 2。
到底什么時候控制臺I/O 會延遲,甚至是否能夠被觀察到,這都是游移不定的。
所以如果在調(diào)試的過程中遇到對象在console.log(..) 語句之后被修改,可你卻看到了意料之外的結(jié)果,要意識到這可能是這種I/O 的異步化造成的。
書中建議:
如果遇到這種少見的情況,最好的選擇是在JavaScript 調(diào)試器中使用斷點,而不要依賴控制臺輸出。次優(yōu)的方案是把對象序列化到一個字符串中,以強制執(zhí)行一次“快照”,比如通過JSON.stringify(..)。
結(jié)論:
由此可見,console.log打印出來的內(nèi)容并不是一定百分百可信的內(nèi)容。一般對于基本類型number、string、boolean、null、undefined的輸出是可信的。但對于Object等引用類型來說,則就會出現(xiàn)上述異常打印輸出。所以對于一般基本類型的調(diào)試,調(diào)試時使用console.log來輸出內(nèi)容時,不會存在坑。但調(diào)試對象時,最好還是使用打斷點(debugger)這樣的方式來調(diào)試更好。