一張紙搞懂JS系列(3)之垃圾回收機制,記憶體洩漏,閉包
javascript專欄為大家介紹垃圾回收機制,記憶體洩漏,閉包的內容,快端小板凳來看看囉。
寫在最前面:這是javascript專欄我即將開始寫的一個系列,主要是在框架橫行的時代,雖然上班用的是框架,但是對於面試,以及技術進階,JS基礎知識的鋪墊是錦上添花,也是不得不學習的一塊知識,雖然開汽車的不需要很懂汽車,只需要掌握汽車的常用功能即可。但是如果你懂車,那你也能更好地開車,同理。當然,一篇文章也不會光光只講一個知識點,一般會將有關聯的知識點串聯起來,一邊記錄自己的學習,一邊分享自己的學習,互勉!如果可以的話,也請給我按讚,你的讚也能讓我更努力地更新!
概覽
- 食用時間:6-12分鐘
- 難度:簡單,別跑,看完再走
垃圾回收機制
前面一篇部落格主要講解了記憶體的分配和使用(棧記憶體與堆內存,深拷貝與淺拷貝),使用完了以後,當然是要將不使用的記憶體歸還,就像將手機上不使用的軟體從後台清除,可以提升手機的運行速度,不然越來越多,遲早會卡, JS
也是一樣的。
每隔一段時間,JS
的垃圾收集器都會對變數進行“巡邏”,就和保安巡邏園區一樣,讓不相干的人趕緊走。當一個變數不被需要了以後,它就會把這個變數所佔用的記憶體空間釋放,這個過程就叫做垃圾回收
##JS 的垃圾回收演算法分為兩種,引用計數法和標記清除法
- 引用計數法引用計數法是最初級的垃圾回收演算法,已經被現代瀏覽器所淘汰了。在學習引用計數法之前,需要先對
引用有一定的概念,你可以認為它就是對當前變數所指向的那塊記憶體位址的描述,有點類似於JS引用資料型別的記憶體指向的概念,先來看一行程式碼:
當我們在給var obj={name:'jack'};复制代码
登入後複製obj
#而當我們給賦值的同時,其實就創建了一個指向該變數的
引用,引用計數為1,在引用計數法的機制下,記憶體中的每一個值都會對應一個引用計數obj
我們知道,函數作用域的生命週期是很短暫的,在函數執行完畢之後,裡面的變數基本上是沒用的變數了,不清除的後果就是該記憶體垃圾沒有被釋放,依然霸占著原有的記憶體不鬆手,就會容易引發賦值為
null時,這個變數就變成了一塊沒用的內存,那麼此時,
obj的引用計數將會變成
0,它將會被垃圾收集器所回收,也就是obj所佔用的記憶體空間將會被釋放
記憶體洩漏,先來看一段程式碼以及運行結果:
function changeName(){ var obj1={}; var obj2={}; obj1.target=obj2; obj2.target=obj1; obj1.age=15; console.log(obj1.target); console.log(obj2.target); } changeName();复制代码
登入後複製 obj1.target我們可以看到, 和
obj2.target存在互相引用的情況,因為在改變
obj1.age的同時,
obj1.target.age和
obj2.target.age也同時都被影響到了,它們所指向的引用計數是一致的
在函數執行完畢的時候,obj1
和
obj2還是活的好好地,因為
obj1.target和
obj2.target的引用計數在執行完畢之後,仍然是
1 ,明明函數執行完畢,但是這種垃圾依然存在,這種函數定義多了,記憶體洩漏也會是無法避免的 - 標記清除法上面的引用計數法的弊端已經很明顯了,那麼,現在所要說的標記清除法就不存在這樣子的問題。因為它所採用的判斷標準是看這個物件
是否可抵達,它主要分為兩個階段,標記階段和清除階段:
- #標記階段垃圾收集器會從根物件(Window物件)出發,掃描所有可以觸及的對象,這就是所謂的
可抵達
- 清除階段
在掃描的同時,根對象無法觸及(
不可抵達)的對象,就是被認為不被需要的對象,就會被當成垃圾清除
现在再来看下上面的代码
function changeName(){ var obj1={}; var obj2={}; obj1.target=obj2; obj2.target=obj1; obj1.age=15; console.log(obj1.target); console.log(obj2.target); } changeName();复制代码
登入後複製在函数执行完毕之后,函数的声明周期结束,那么现在,从
Window对象
出发,obj1
和obj2
都会被垃圾收集器标记为不可抵达,这样子的情况下,互相引用的情况也会迎刃而解。 - #標記階段垃圾收集器會從根物件(Window物件)出發,掃描所有可以觸及的對象,這就是所謂的
内存泄漏
该释放的内存垃圾没有被释放,依然霸占着原有的内存不松手,造成系统内存的浪费,导致性能恶化,系统崩溃等严重后果,这就是所谓的内存泄漏
闭包
定义与特性
闭包是指有权访问另一个函数作用域中的变量的函数。至于为什么有权访问,主要是因为作用域嵌套作用域,也就是所谓的作用域链,关于作用域链不清楚的可以看我的第一篇博客一文搞懂JS系列(一)之编译原理,作用域,作用域链,变量提升,暂时性死区,就是因为作用域链的存在,所以内部函数才可以访问外部函数中定义的变量 ,作用域链是向外不向内的,探出头去,向外查找,而不是看着锅里,所以外部函数是无法访问内部函数定义的变量的。并且,还有一个特性就是将闭包内的变量始终保持在内存中。
前面的作用域向外不向内,这里就不再做过多解释了,我们主要来看我后面说的特性,那就是闭包内的变量始终保存在内存中
来看一下阮一峰教程当中的一个例子
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ console.log(n); } return f2; } var result=f1(); //等同于return f2(); result(); // 999 nAdd(); result(); // 1000 nAdd(); result(); // 1000复制代码
登入後複製从输出结果就可以看得出来,这个变量
n
就一直保存在内存中,那么,为什么会这样子呢,我们现在就来逐步地分析代码① 首先
f1()
作为f2()
的父函数,根据作用域链的规则,nAdd()
方法以及f2()
方法中可以正常访问到n
的值②
f2()
被赋予了一个全局变量,可能这里大家就会开始产生疑惑了,这个f2()
不是好好地定义在了f1()
函数中吗,这不是扯淡吗,那么,先看下面的这句var result=f1();
,这个result
很明显是被赋予了一个全局变量,这应该是没有任何争议的,那么,接着来看这个f1()
,可以看到最后,是一句return f2;
,看到这里,想必大家也已经想明白了,这个f2()
被赋予了一个全局变量③ 已经明白了上面的这一点以后,根据上面垃圾回收机制所提及到的标记清除法,这个
f2()
始终是可以被根对象Window
访问到的,所以 f2 将始终存在于内存之中,而 f2 是依赖于 f1 ,因此 f1 也将始终存在于内存当中,那么,n
的值也就自然始终存在于内存当中啦④ 还有一点需要注意的就是为什么我们可以直接执行
nAdd()
,这是因为在nAdd()
的前面没有使用var
,因此nAdd()
是一个全局函数而不是局部函数所以,闭包的变量会常驻内存,滥用闭包容易造成内存泄漏,特别是在 IE 浏览器下,2020年了,应该没人使用 IE 了吧(小声bb),解决办法就是在退出函数之前,将不使用的局部变量全部删除,这也是上面讲了垃圾回收机制 => 内存泄漏,再讲到闭包的原因,我会尽量将有关联性的知识点一起讲了,也方便大家学习和加深印象。
系列目录
一张纸懂JS系列(1)之编译原理,作用域,作用域链,变量提升,暂时性死区
一张纸搞懂JS系列(2)之JS内存生命周期,栈内存与堆内存,深浅拷贝
一张纸搞懂JS系列(3)之垃圾回收机制,内存泄漏,闭包
相关免费学习推荐:javascript(视频)
以上是一張紙搞懂JS系列(3)之垃圾回收機制,記憶體洩漏,閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

閉包是一種巢狀函數,它能存取外層函數作用域的變量,優點包括資料封裝、狀態保持和靈活性。缺點包括記憶體消耗、效能影響和調試複雜性。此外,閉包還可以建立匿名函數,並將其作為回調或參數傳遞給其他函數。

Valgrind透過模擬記憶體分配和釋放來偵測記憶體洩漏和錯誤,使用步驟如下:安裝Valgrind:從官方網站下載並安裝適用於您作業系統的版本。編譯程式:使用Valgrind標誌(如gcc-g-omyprogrammyprogram.c-lstdc++)編譯程式。分析程式:使用valgrind--leak-check=fullmyprogram指令分析已編譯的程式。檢查輸出:Valgrind將在程式執行後產生報告,顯示記憶體洩漏和錯誤訊息。

記憶體洩漏會導致Go程式記憶體不斷增加,可通過:關閉不再使用的資源,如檔案、網路連線和資料庫連線。使用弱引用防止記憶體洩漏,當物件不再被強引用時將其作為垃圾回收目標。利用go協程,協程棧記憶體會在退出時自動釋放,避免記憶體洩漏。

C++中記憶體洩漏是指程式分配了記憶體但忘記釋放,導致記憶體無法被重複使用。偵錯技術包括使用偵錯器(如Valgrind、GDB)、插入斷言和使用記憶體洩漏偵測器函式庫(如Boost.LeakDetector、MemorySanitizer)。透過實作案例展示了使用Valgrind檢測記憶體洩漏,並提出了避免記憶體洩漏的最佳做法,包括:始終釋放分配的記憶體、使用智慧指標、使用記憶體管理庫和定期進行記憶體檢查。

Java中的閉包允許內部函數存取外部的作用域變量,即使外部函數已經退出。透過匿名內部類別實現,內部類別持有一個外部類別的引用,使外部變數保持活動。閉包增強了程式碼靈活性,但需要注意記憶體洩漏風險,因為匿名內部類別對外部變數的參考會保持這些變數的活動狀態。

Go語言函數閉包在單元測試中發揮著至關重要的作用:捕獲值:閉包可以存取外部作用域的變量,允許在巢狀函數中捕獲和重複使用測試參數。簡化測試程式碼:透過擷取值,閉包消除了對每個循環重複設定參數的需求,從而簡化了測試程式碼。提高可讀性:使用閉包可以組織測試邏輯,使測試程式碼更清晰、更易於閱讀。

要查找C++中的記憶體洩漏,可以利用Valgrind和AddressSanitizer。 Valgrind動態偵測洩漏,顯示位址、大小和呼叫堆疊。 AddressSanitizer是一個Clang編譯器插件,偵測記憶體錯誤和洩漏。若要啟用ASan洩漏檢查,請在編譯時使用--leak-check=full選項,該選項將在程式執行後報告洩漏。
