前端程序員幾乎天天不是在打log就是在打log的路上,最近就在本身真實項目中遇到了一個console.log
的坑,由此記錄一下console.log
過程當中你須要瞭解的知識,少走彎路。javascript
不少人在開發過程當中利用console.log
調試都被它「騙過」,在監視一些複雜對象的時候,log呈現給咱們的值可能與預期不符,以下:
這時有人直接下定義:console.log
是異步的!其實這個問題在《你不知道的javascript中卷》第二部分異步和性能1.1節異步控制檯部分有說起:前端
並無什麼規範或一組需求指定console.* 方法族如何工做——它們並非JavaScript 正式的一部分,而是由宿主環境(請參考本書的「類型和語法」部分)添加到JavaScript 中的。所以,不一樣的瀏覽器和JavaScript 環境能夠按照本身的意願來實現,有時候這會引發混淆。
尤爲要提出的是,在某些條件下,某些瀏覽器的console.log(..) 並不會把傳入的內容當即輸出。出現這種狀況的主要緣由是,在許多程序(不僅是JavaScript)中,I/O 是很是低速的阻塞部分。因此,(從頁面/UI 的角度來講)瀏覽器在後臺異步處理控制檯I/O 可以提升性能,這時用戶甚至可能根本意識不到其發生。
咱們得知道一點,JS中對象是引用類型,每次使用對象時,都只是使用了對象在堆中的引用。vue
當咱們在使用a.b.c = 2
改變了對象的屬性值時,它在堆中c的值也變成了2,而當咱們不展開對象看的時候,console.log
打印的是對象當時的快照,因此咱們看到的c屬性值是沒改變以前的1,展開對象時,它實際上是從新去內存中讀取對象的屬性值,因此當咱們展開對象後看到的c屬性值是2。java
因而可知,console.log
打印出來的內容並非必定百分百可信的內容。通常對於基本類型number
、string
、boolean
、null
、undefined
的輸出是可信的。但對於Object等引用類型來講,則就會出現上述異常打印輸出。
若是改成console.log(JSON.stringfy(obj))
則會打印出當前對象的快照,也就能拿到相似同步下的理想結果。更好的解決方案是使用斷點進行調試,在那裏執行徹底中止,您能夠檢查每一個點的當前值。僅對可序列化和不可變數據使用日誌記錄。node
經過上面的問題,能夠反思一下:爲何你能夠在控制檯打印程序變量的快照?而且是引用類型的值時,展開對象會從新去內存讀取對象的值?程序運行完後難道變量不該該被gc回收嗎?python
在傳遞給console.log
的對象是不能被垃圾回收 ♻️,由於在代碼運行以後須要在開發工具能查看對象信息。因此最好不要在生產環境中console.log
任何對象。git
能夠在控制檯印證這一點:程序員
var Foo = function() {}; console.log(new Foo());
打開控制檯Memory調試:github
<script> ... mounted () { this.handleSignCallback({ type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS, success: true, result: { longStr: new Array(1000000).join('*'), someMethod: function () { } } }); }, methods: { handleSignCallback(data) { const { type, result, success } = data; // do something... console.log(result) // 返回結果打印 if (true) { // 真實項目中用來判斷是否知足條件 this.timer = setTimeout(() => { this.handleSignCallback({ type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS, success: true, result: { longStr: new Array(1000000).join('*'), someMethod: function () { } } }) clearTimeout(this.timer); }, 1000) } } } </script>
真實項目在輪詢中須要判斷某些邏輯,知足條件了,會延遲幾秒繼續去請求後臺,處理一些數據,直到返回的對象沒有知足條件的數據才中止去請求拉數據,期間我想打印返回的數據,因而我加了console.log(result)
,可是若是數據量大的話,執行一會就會程序崩潰,打開調試控制檯->Memory:npm
js Heap在一直上升,無上限,直至瀏覽器崩潰;
可是當我註釋console.log(result)
這行 時,再次觀察堆內存狀況:
這時堆內存使用狀況基本穩定在150左右。
先給上述問題下個結論:console.log
確實會影響網頁性能。原因請結合下述展開問題結合深究。
針對結論二,你們應該都知道了,生產模式下儘可能不要留console.log
,也許你們如今公司裏可能都是這麼作的,可是你真的知道爲何這麼作嗎?下面舉例Vue
項目如何統一去除坑爹的console.log
:
安裝babel-plugin-transform-remove-console
npm i babel-plugin-transform-remove-console --dev
配置babel.config.js
插件
// babel.config.js文件 const plugins = [] // 生產環境移除console if(process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') { plugins.push("transform-remove-console") } module.exports = { presets: [ '@vue/app' ], plugins: [...plugins] }
這裏結合Chrome的Devtools–>Performance作一些分析,操做步驟以下:
中止記錄這裏結合Chrome的Devtools–>Performance作一些分析,操做步驟以下:
說明:測試代碼因爲模擬輪詢場景,定時器觸發頻繁,可能出現gc的時間點js Heap高於基準線一點,主要看最後一次的js Heap跟第一次觸發的線是否是差很少持平
能夠看到這裏的js Heap(藍色線,綠色線是nodes節點數量)一直在升高,而且我手動回收垃圾(gc)也無濟於事 ,很明顯,發生了內存泄露!
關閉log後,觀察js堆內存狀況如上:能夠看到,手動GC後兩個時間點,js Heap基本恢復到同一水平線。
在普通場景下測試,例如開啓記錄->gc->點擊事件,建立對象(N遍)->gc->中止記錄,js Heap兩個gc時間點基本重合。
Heap Profiling能夠記錄當前的堆內存(heap)的快照,並生成對象的描述文件,該描述文件給出了當時JS運行所用的全部對象,以及這些對象所佔用的內存大小、引用的層級關係等等。
JS運行的時候,會有棧內存(stack)和堆內存(heap),當咱們new一個類的時候,這個new出來的對象就保存在heap裏,而這個對象的引用則存儲在stack裏。程序經過stack的引用找到這個對象。例如:var a = [1,2,3]
,a是存儲在stack中的引用,heap裏存儲着內容爲[1,2,3]的Array對象。
打開調試工具,點擊Memory中的Profiles標籤,選中「Take Heap Snapshot」,點擊「start」按鈕,就能夠拍在當前JS的heap快照了。
右邊視圖中列出了heap裏的對象列表。
constructor:類名
Distance:對象到根的引用層級距離
Objects Count:該類的對象數
Shallow Size:對象所佔內存(不包含內部引用的其餘對象所佔的內存)
Retained Size:對象所佔的總內存(包含····················································)
點擊上圖左上角的黑圈圈,會出現第二個內存快照
將上圖框框切換到comparison(對照)選項,該視圖列出了當前視圖與上一個視圖的對象差別
New:新建了多少對象
Deleted:回收了多少對象
Delta:新建的對象個數減去回收的對象個數
重點看closure(閉包),若是#Delta爲正數,則表示建立了閉包函數,若是多個快照中都沒有變負數,則表示沒有銷燬閉包。
while (true) console.log("hello... there");
運行以上代碼,在大約 3 分鐘左右的時間內,節點消耗了 1.5GB 的內存。
有人會說,死循環了能不引發內存飆升嗎?
那看下面這個:
def hello(): while True: print "hello world..." hello()
Python
永遠保持在 3.3MB。
有人可能會想到前面說的,console.log
會引發內存泄露,可是前面說了是引用類型會形成內存泄露,這裏直接打印字符串,爲何性能也會這麼差?
簡短回答:控制檯輸出是緩衝和異步的。而且除了全局內存限制外,緩衝區不受任何限制。所以,它填滿緩衝區並死亡。
更多信息在#1741。
「緩衝區」是指其實是異步操做隊列的隱式緩衝區。
console.log()
緩衝有限數量的消息Libuv
能夠緩衝消息。這是您能夠經過調整寫入速度來解決的問題。
Libuv
:
Libuv是一個高性能的,事件驅動的異步I/O庫,它自己是由C語言編寫的,具備很高的可移植性。
node開發時,在咱們寫入大型日誌條目的狀況下,寫入文件進行日誌記錄是迄今爲止最好的方法。服務稍後讀取日誌文件並將其聚合到適當的服務。