這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 很多前端都喜歡用 console.log 調試,先不談調試效率怎麼樣,首先 console.log 有個致命的問題:會導致記憶體泄漏。 為什麼這麼說呢? 用 Performance 和 Memory 工具分析下就知道了。 我們準備這樣一段代 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
很多前端都喜歡用 console.log 調試,先不談調試效率怎麼樣,首先 console.log 有個致命的問題:會導致記憶體泄漏。
為什麼這麼說呢?
用 Performance 和 Memory 工具分析下就知道了。
我們準備這樣一段代碼:
一個按鈕,點擊之後創建一個數組,執行一些計算。
很常見的邏輯。
我們最後加了一個 console.log 列印了下這個數組。
起個靜態服務:
瀏覽器訪問:
點擊 performance 下的垃圾回收按鈕,手動觸發一次 GC:
勾選 Memory,然後開始錄製,點擊 3 次按鈕,再執行一次 GC:
你會發現記憶體是這樣的:
記憶體占用有三次增長,因為我們點擊三次按鈕的時候會創建 3 次大數組。
但是最後我們手動 GC 之後並沒有回落下去,也就是這個大數組沒有被回收。
按理來說,代碼執行完,那用的記憶體就要被釋放,然後再執行別的代碼,結果這段代碼執行完之後大數組依然占據著記憶體,這樣別的代碼再執行的時候可用記憶體就少了。
這就是發生了記憶體泄漏,也就是代碼執行完了不釋放記憶體的流氓行為。
有同學說,只是這麼一點記憶體問題不大呀,反正可用記憶體還很多。
但如果你的代碼要跑很長時間,這段代碼要執行很多次呢?
每次執行都會占據一部分記憶體不釋放,慢慢的記憶體就不夠用了,甚至會導致程式崩潰。
比如當這段代碼執行個 9 次,記憶體占用就增長了 9 個大數組的記憶體:
再多執行幾次呢?
是不是就有崩潰的隱患了。
那為啥說是 console.log 導致的呢?
我們來看看不用 console.log 是什麼樣的:
註釋掉 console.log,重新跑。
你會發現現在的記憶體分配情況是這樣的:
分配了三次記憶體,但是 GC 後又會落下去了。
這才是沒有記憶體泄漏的好代碼。
那為啥 console.log 會導致記憶體泄漏呢?
因為控制台列印的對象,你是不是有可能展開看?那如果這個對象在記憶體中沒有了,是不是就看不到了?
所以有這個引用在,瀏覽器不會把你列印的對象的記憶體釋放掉。
有的同學說,那我不打開控制台,是不是就沒有這個引用了?
答案是否定的:
我點擊了幾次之後,再打開控制台,依然是可以看到這個對象的,說明沒有被 GC。
也就是說用 console.log 列印對象的代碼一定是有記憶體泄漏的。
當然,也不只是 console.log 會導致記憶體泄漏,還有別的 4 種情況:
- 定時器用完了沒有清除,那每次執行都會多一個定時器的記憶體占用,這就是記憶體泄漏
- 元素從 dom 移除了,但是還有一個變數引用著他,這樣的游離的 dom 元素也不會被回收。每執行一次代碼,就會多出游離的 dom 元素的記憶體,這也是記憶體泄漏
- 閉包引用了某個變數,這個變數不會被回收,如果這樣的閉包比較多,那每次執行都會多出這些被引用變數的記憶體占用。這樣引用大對象的閉包多了之後,也會導致記憶體問題
- 全局變數,這個本來就不會被 GC,要註意全局變數的使用
總之,全局變數、閉包引用的變數、被移除的 dom 依然被引用、定時器用完了沒清除、console.log 都會發生代碼執行完了,但是還占用著一部分記憶體的流氓行為,也就是記憶體泄漏。
註意,這裡指的是使用完畢後沒有回收,在使用期間的記憶體增長是正常的。
那怎麼排查呢?
performance 工具就可以:
點擊記憶體分配情況的某個點,就會定位到 performance 中的某個任務的代碼,點擊可以在下麵看到詳情:
這樣就定位到了分配記憶體的代碼,分析一下哪裡會有問題即可。
當然,前提還是要執行先 GC,再做一些操作,再 GC 的這個流程。
這是從代碼角度來分析記憶體泄漏,其實還可以從記憶體中對象的角度,這個是通過 Memory 工具:
先 GC,錄製一次記憶體快照,再點擊幾次按鈕,然後 GC,再錄製一次記憶體快照。
流程和用 performance 分析的時候一樣。
拿到兩次記憶體快照也是可以分析出有記憶體泄漏的:
可以看到 GC 後記憶體占用依然增長了。
快照記錄著這個時刻記憶體中所有對象的狀態:
對比兩次快照,就可以找到變化的部分:
比如這時候可以看到最大的記憶體增長是 array 對象:
然後就可以從 array 的角度去思考是什麼導致的記憶體泄漏了。
此外,memory 還有實時分析的工具:
選擇第二個,然後點幾次按鈕:
其實不用手動 GC,JS 引擎會做 GC。
去掉 console.log 再錄製是這樣的:
除了最開始全局變數會分配一些記憶體以外,點擊按鈕之後的記憶體變藍後又變灰了,也就是被 GC 了。
這樣你點多少次按鈕,記憶體占用都沒有增長。
這就是代碼執行完,會回收所有用到的記憶體的好代碼。
而前面的那個是每次代碼執行,都會占用一部分記憶體不釋放的記憶體泄漏代碼。
你還可以看到每一次記憶體分配的對象是啥:
不管是用 Performance 工具還是 Memory 工具,都可以發現 console.log 有記憶體泄漏的問題。所以還是儘量不要用這個來調試了。
那應該用什麼呢?
用 debugger 呀,不管是 vscode debugger 還是 chrome devtools 的都可以:
你可以添加一個 logpoint 來代替 console.log 列印:
代碼執行到這裡就會列印:
而你的代碼里不需要寫 console.log。
此外,很多地方可以用斷點代替列印:
可以看到代碼執行路線和作用域,豈不是更高效?
總結
console.log 會導致記憶體泄漏,也就是代碼執行完了,但還占據著一部分記憶體的流氓行為。
除了 console.log,游離的 dom 被變數引用、全局變數、變數被閉包引用、定時器沒清除也會導致記憶體泄漏。
我們可以用 Performance 工具和 Memory 工具分析記憶體泄漏。
先手動 GC,然後執行一些操作,再 GC,如果記憶體沒有回到執行前,就說明這段代碼有記憶體泄漏,可以再用 Performance 定位到代碼位置分析代碼。
Memory 工具是從記憶體對象的角度分析,可以對兩次快照做 diff,看下是啥對象泄漏了。
也可以實時檢測記憶體占用情況,看看是否存在記憶體泄漏,對象是啥。
console.log 調試效率也不高,可以換成 logpoint,或者打斷點。
千萬不要把 console.log 上生產!不然這樣有記憶體泄漏的代碼,一旦執行時間長了就會有問題。
其實普通項目也還好,不會長期跑,但是類似大屏項目這種長期跑的,一旦有記憶體泄漏,一定會崩潰,只有時間長短的區別。