javascript欄目為大家介紹垃圾回收機制,內存泄漏,閉包的內容,快端小板凳來看看啦。
寫在最前面:這是javascript欄目我即將開始寫的一個系列,主要是在框架橫行的時代,雖然上班用的是框架,但是對于面試,以及技術進階,JS基礎知識的鋪墊是錦上添花,也是不得不學習的一塊知識,雖然開汽車的不需要很懂汽車,只需要掌握汽車的常用功能即可。但是如果你懂汽車,那你也能更好地開車,同理。當然,一篇文章也不會光光只講一個知識點,一般會將有關聯(lián)的知識點串聯(lián)起來,一邊記錄自己的學習,一邊分享自己的學習,互勉!如果可以的話,也請給我點個贊,你的點贊也能讓我更加努力地更新!
概覽
- 食用時間: 6-12分鐘
- 難度: 簡單,別跑,看完再走
垃圾回收機制
前面一篇博客主要講解了內存的分配和使用(棧內存與堆內存,深拷貝與淺拷貝),使用完了以后,當然是要將不使用的內存歸還,就像將手機上不使用的軟件從后臺清除,可以提升手機的運行速度,不然越來越多,遲早會卡, JS
也是一樣的。
每隔一段時間, JS
的垃圾收集器都會對變量進行“巡邏”,就和保安巡邏園區(qū)一樣,讓不相干的人趕緊走。當一個變量不被需要了以后,它就會把這個變量所占用的內存空間所釋放,這個過程就叫做垃圾回收
JS
的垃圾回收算法分為兩種,引用計數(shù)法和標記清除法
-
引用計數(shù)法
引用計數(shù)法是最初級的垃圾回收算法,已經被現(xiàn)代瀏覽器所淘汰了。在學習引用計數(shù)法之前,需要首先對引用有一定的概念,你可以認為它就是對當前變量所指向的那塊內存地址的描述,有點類似于JS引用數(shù)據類型的內存指向的概念,先來看一行代碼:
var obj={name:'jack'};復制代碼
當我們在給
obj
賦值的同時,其實就創(chuàng)建了一個指向該變量的引用,引用計數(shù)為1,在引用計數(shù)法的機制下,內存中的每一個值都會對應一個引用計數(shù)而當我們給
obj
賦值為null
時,這個變量就變成了一塊沒用的內存,那么此時,obj
的引用計數(shù)將會變成 0,它將會被垃圾收集器所回收,也就是obj
所占用的內存空間將會被釋放我們知道,函數(shù)作用域的生命周期是很短暫的,在函數(shù)執(zhí)行完畢之后,里面的變量基本是沒用的變量了,不清除的后果就是該內存垃圾沒有被釋放,依然霸占著原有的內存不松手,就會容易引發(fā)內存泄漏,先來看一段代碼以及運行結果:
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
也同時都被影響到了,它們所指向的引用計數(shù)是一致的在函數(shù)執(zhí)行完畢的時候,
obj1
和obj2
還是活的好好地,因為obj1.target
和obj2.target
的引用計數(shù)在執(zhí)行完畢之后,仍然是 1 ,明明函數(shù)執(zhí)行完畢,但是這種垃圾依然存在,這種函數(shù)定義多了,內存泄漏也會是無法避免的 -
標記清除法
上面的引用計數(shù)法的弊端已經很明顯了,那么,現(xiàn)在所要說的標記清除法就不存在這樣子的問題。因為它采用的判斷標準是看這個對象是否可抵達,它主要分為兩個階段,標記階段和清除階段:
-
標記階段
垃圾收集器會從根對象(Window對象)出發(fā),掃描所有可以觸及的對象,這就是所謂的可抵達
-
清除階段 在掃描的同時,根對象無法觸及(不可抵達)的對象,就是被認為不被需要的對象,就會被當成垃圾清除
現(xiàn)在再來看下上面的代碼
function changeName(){ var obj1={}; var obj2={}; obj1.target=obj2; obj2.target=obj1; obj1.age=15; console.log(obj1.target); console.log(obj2.target); } changeName();復制代碼
在函數(shù)執(zhí)行完畢之后,函數(shù)的聲明周期結束,那么現(xiàn)在,從
Window對象
出發(fā),obj1
和obj2
都會被垃圾收集器標記為不可抵達,這樣子的情況下,互相引用的情況也會迎刃而解。 -
內存泄漏
該釋放的內存垃圾沒有被釋放,依然霸占著原有的內存不松手,造成系統(tǒng)內存的浪費,導致性能惡化,系統(tǒng)崩潰等嚴重后果,這就是所謂的內存泄漏
閉包
-
定義與特性
閉包是指有權訪問另一個函數(shù)作用域中的變量的函數(shù)。至于為什么有權訪問,主要是因為作用域嵌套作用域,也就是所謂的作用域鏈,關于作用域鏈不清楚的可以看我的第一篇博客一文搞懂JS系列(一)之編譯原理,作用域,作用域鏈,變量提升,暫時性死區(qū),就是因為作用域鏈的存在,所以內部函數(shù)才可以訪問外部函數(shù)中定義的變量 ,作用域鏈是向外不向內的,探出頭去,向外查找,而不是看著鍋里,所以外部函數(shù)是無法訪問內部函數(shù)定義的變量的。并且,還有一個特性就是將閉包內的變量始終保持在內存中。
前面的作用域向外不向內,這里就不再做過多解釋了,我們主要來看我后面說的特性,那就是閉包內的變量始終保存在內存中
來看一下阮一峰教程當中的一個例子
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
就一直保存在內存中,那么,為什么會這樣子呢,我們現(xiàn)在就來逐步地分析代碼① 首先
f1()
作為f2()
的父函數(shù),根據作用域鏈的規(guī)則,nAdd()
方法以及f2()
方法中可以正常訪問到n
的值②
f2()
被賦予了一個全局變量,可能這里大家就會開始產生疑惑了,這個f2()
不是好好地定義在了f1()
函數(shù)中嗎,這不是扯淡嗎,那么,先看下面的這句var result=f1();
,這個result
很明顯是被賦予了一個全局變量,這應該是沒有任何爭議的,那么,接著來看這個f1()
,可以看到最后,是一句return f2;
,看到這里,想必大家也已經想明白了,這個f2()
被賦予了一個全局變量③ 已經明白了上面的這一點以后,根據上面垃圾回收機制所提及到的標記清除法,這個
f2()
始終是可以被根對象Window
訪問到的,所以 f2 將始終存在于內存之中,而 f2 是依賴于 f1 ,因此 f1 也將始終存在于內存當中,那么,n
的值也就自然始終存在于內存當中啦④ 還有一點需要注意的就是為什么我們可以直接執(zhí)行
nAdd()
,這是因為在nAdd()
的前面沒有使用var
,因此nAdd()
是一個全局函數(shù)而不是局部函數(shù)所以,閉包的變量會常駐內存,濫用閉包容易造成內存泄漏,特別是在 IE 瀏覽器下,2020年了,應該沒人使用 IE 了吧(小聲bb),解決辦法就是在退出函數(shù)之前,將不使用的局部變量全部刪除,這也是上面講了垃圾回收機制 => 內存泄漏,再講到閉包的原因,我會盡量將有關聯(lián)性的知識點一起講了,也方便大家學習和加深印象。
系列目錄
-
一張紙懂JS系列(1)之編譯原理,作用域,作用域鏈,變量提升,暫時性死區(qū)
-
一張紙搞懂JS系列(2)之JS內存生命周期,棧內存與堆內存,深淺拷貝
-
一張紙搞懂JS系列(3)之垃圾回收機制,內存泄漏,閉包
相關免費學習推薦:javascript(視頻)