JavaScript這幾種內存泄露寫法,你要小心了
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
今天我想和你聊聊,前端開發過程中內存泄露的問題。相信你在工作當中遇到過這樣的情況,比如,相同的代碼在開發環境運行得好好的,到線上運行一段時間后就出現頁面運行卡頓,比較嚴重時,無用的內存會持續遞增,從而導致整個系統卡頓,甚至崩潰。那么,這大概率是發生了內存泄漏。 那么,你可以思考幾個問題:
好,帶著這三個問題,我們來學習今天的內容。 什么是內存泄漏?首先什么是內存泄漏呢?是放在內存中的變量憑空消失了嗎?其實并不是,而是不再使用的內存沒有得到釋放,導致內存一直在增加。我打個簡單的比方:你跟別人要東西,你不停地要,不停地要,當你不需要這些東西的時候你也不還給別人,這就是內存泄漏。 那為什么會出現內存泄漏呢,如果你了解內存的生命周期,一定會知道是哪個環節導致了內存泄漏。不管什么程序語言,它的生命周期一般可以按順序分為三個部分: 我們先來簡單了解下這三個部分都做了什么事情:第一部分是內存分配,當我們創建一個函數或者變量時,必須為它分配一定數量的內存;第二部分是內存的使用,對分配的內存進行讀和寫操作;第三部分是內存釋放階段,將不需要的內存進行釋放。 所有語言的第二部分都是明確的。第一和第三部分在 C 或者 C++ 這類底層語言中是明確的,但在像 JavaScript 這些高級語言中,大部分都是隱含的。JavaScript 是在創建變量時自動進行了分配內存,并且在不使用它們時“自動”釋放,釋放的過程稱為垃圾回收。 問題就出現在這個“自動”的垃圾回收,讓前端開發者錯誤地以為他們可以不關心內存管理。但是實際上瀏覽器 V8 引擎的垃圾回收只能解決一般問題,它自身有一些局限性。 如果你了解標記 - 清除垃圾回收算法,就會知道,它是首先從根開始將可能被引用的對象用遞歸的方式進行標記,然后將沒有標記到的對象作為垃圾進行回收。 這個算法假定設置一個叫做根的對象,在 JavaScript 里,根就是是全局對象 window。垃圾回收器將定期從全局對象 window 開始,找所有從 window 開始引用的對象,然后找這些對象引用的對象……遞歸完成后,垃圾回收器將找到所有“可獲得”的對象和收集所有“不能獲得”的對象,最后將不可獲得的對象進行回收。 但是垃圾回收機制會有這樣一個問題:假如有一些對象我們已經不需要使用了,但是仍然能被訪問到,我們沒有對它進行手動清除,那么瀏覽器引擎的就不會對這個對象回收,當無用的對象越來越多,就會導致內存泄漏。那么哪些寫法會導致內存泄漏呢?我總結了 JavaScript 中四類常見的內存泄漏寫法和避免方法,給你參考。 哪些寫法會導致內存泄漏?1.未聲明/意外的全局變量第一種,未聲明或者意外的全局變量。全局變量的生命周期最長,直到頁面關閉前,它都存活著,所以全局變量上的內存一直都不會被回收。所以當我們寫代碼的時候如果全局變量使用不當,沒有及時回收,或者拼寫錯誤將某個變量掛載到全局變量時,就會發生內存泄漏了。
這段代碼中,test 函數中定義了一個變量 b,沒有使用 var 或者 let 變量進行聲明,這時 b 會成為全局變量,test 執行后變量 b 不會被回收。解決方式也比較簡單,在 JavaScript 文件中添加'use strict',開啟嚴格模式,這個時候就不能使用 b 這個意外的全局變量了,開發時就會在瀏覽器控制臺報錯,避免這種情況發生。
2.遺忘的定時器第二類常見的內存泄漏是定時器 setTimeout 和 setInterval,它的生命周期是由瀏覽器專門的線程來維護的,所以當在某個頁面使用了定時器,并且這個頁面銷毀時,如果你沒有手動去釋放清理這些定時器,那么這些定時器還是存活著的。 來看一個示例,這段代碼通過定時器注冊了一個回調函數,該回調函數內又持有當前頁面 ID 為 Node 的 DOM 元素。當頁面銷毀之后,由于定時器持有該頁面部分引用而造成定時器沒有被回收,進而導致定時器內部的數據 someData 也無法被回收,就導致了內存泄漏。
解決辦法是,在不使用定時器的時候將定時器取消,setInterval 設置一個 ID,然后就可以通過 clearInterval(id) 進行取消了。
3.事件綁定第三種由“事件綁定”導致的內存泄漏也非常常見,一般是由于事件響應函數沒有及時移除,導致重復綁定或者 DOM 元素已經移除后未處理事件響應函數造成的,例如這段 React 代碼:
組件在掛載的時候監聽了 resize 事件,但是在組件移除的時候沒有處理相應函數,假如 的掛載和移除非常頻繁,那么就會在 window 上綁定很多無用的事件監聽函數,最終導致內存泄漏。 那怎么解決呢?我們可以通過在組件卸載 componentWillUmout 的時候移除監聽事件來避免這個問題:
4.閉包最后一類比較重要,也是我們在開發過程中經常使用到的,那就是閉包。這里先聲明:閉包本身沒有錯,不會引起內存泄漏,而是我們使用錯誤導致的。 我們都知道,閉包有兩個主要作用,一是延伸變量作用域范圍,讀取函數內部的變量。二是讓這些變量的值始終保持在內存中。簡單理解就是,一個作用域可以訪問另外一個函數內部的局部變量。 那么,為什么會說閉包可能會導致內存泄漏呢?函數本身會持有它定義時所在的詞法環境的引用,但通常情況下,使用完函數后,該函數所申請的內存都會被回收了。但當函數內再返回一個函數時,由于返回的函數持有外部函數的詞法環境,而返回的函數又被其他生命周期東西所持有,導致外部函數雖然執行完了,但內存卻無法被回收。 我們舉個例子來看一下閉包,這個例子中函數 f1 里面返回了一個函數 f2,f2 中使用了 f1 函數中的變量 n,這樣就形成了閉包。你可以想一下 console.log 的內容應該是什么?好,答案是在執行完 f1 函數后,第一次調用 result 結果返回 1000,第二次調用結果返回了 1001。這說明了什么呢?說明變量 n 在函數執行完并沒有被銷毀,而是繼續留在了內存中。
正常來說,閉包并不是內存泄漏,因為這種持有外部函數詞法環境本就是閉包的特性,就是為了讓這塊內存不被回收,因為可能在未來還需要用到,但這無疑會造成內存的消耗,所以,我們不應該濫用閉包。 總結今天的分享到這里就結束了,最后我們來回顧一下今天講的內容。內存泄漏發生在 JavaScript 內存自動回收階段,瀏覽器引擎在“自動”回收階段使用的是標記清除算法,可以將“不可獲得”的對象進行回收,如果我們編寫代碼的時候對一些全局變量處理不當,定時器和事件綁定沒有及時清除,或者閉包使用不當,就會引起內存泄漏問題。所以為了避免內存泄漏,最重要對一點就是養成良好對編程習慣,比如內存分配后,一定要注意寫好內存釋放的代碼,有借有還,才能高效運轉,再借不難。 ?轉自https://juejin.cn/post/7490856819004620836 該文章在 2025/4/14 9:56:00 編輯過 |
關鍵字查詢
相關文章
正在查詢... |