javascript
JS高级——内存管理和闭包
0、預(yù)備知識(shí)
0.1 認(rèn)識(shí)內(nèi)存管理
不管什么樣的編程語言,在代碼的執(zhí)行過程中都是需要給它分配內(nèi)存的,不同的是某些編程語言需要我們自己手動(dòng)的管理內(nèi)存,某些編程語言會(huì)可以自動(dòng)幫助我們管理內(nèi)存:
不管以什么樣的方式來管理內(nèi)存,內(nèi)存的管理都會(huì)有如下的生命周期:
- 第一步:分配申請你需要的內(nèi)存(申請);
- 第二步:使用分配的內(nèi)存(存放一些東西,比如對象等);
- 第三步:不需要使用時(shí),對其進(jìn)行釋放;
不同的編程語言對于第一步和第三步會(huì)有不同的實(shí)現(xiàn):
- 手動(dòng)管理內(nèi)存:比如C、C++,包括早期的OC,都是需要手動(dòng)來管理內(nèi)存的申請和釋放的(malloc和free函數(shù));
- 自動(dòng)管理內(nèi)存:比如Java、JavaScript、Python、Swift、Dart等,它們有自動(dòng)幫助我們管理內(nèi)存;
我們可以知道JavaScript通常情況下是不需要手動(dòng)來管理的。
0.2 JS的內(nèi)存管理
JavaScript會(huì)在定義變量時(shí)為我們分配內(nèi)存。但是內(nèi)存分配方式是一樣的嗎?
- JS對于基本數(shù)據(jù)類型內(nèi)存的分配會(huì)在執(zhí)行時(shí),直接在棧空間進(jìn)行分配;
- JS對于復(fù)雜數(shù)據(jù)類型內(nèi)存的分配會(huì)在堆內(nèi)存中開辟一塊空間,并且將這塊空間的指針返回值變量引用;
0.3 JS的垃圾回收
因?yàn)閮?nèi)存的大小是有限的,所以當(dāng)內(nèi)存不再需要的時(shí)候,我們需要對其進(jìn)行釋放,以便騰出更多的內(nèi)存空間。
在手動(dòng)管理內(nèi)存的語言中,我們需要通過一些方式自己來釋放不再需要的內(nèi)存,比如free函數(shù):
- 但是這種管理的方式其實(shí)非常的低效,影響我們編寫邏輯的代碼的效率;
- 并且這種方式對開發(fā)者的要求也很高,并且一不小心就會(huì)產(chǎn)生內(nèi)存泄露;
所以大部分現(xiàn)代的編程語言都是有自己的垃圾回收機(jī)制:
- 垃圾回收的英文是Garbage Collection,簡稱GC;
- 對于那些不再使用的對象,我們都稱之為是垃圾,它需要被回收,以釋放更多的內(nèi)存空間;
- 而我們的語言運(yùn)行環(huán)境,比如Java的運(yùn)行環(huán)境JVM,JavaScript的運(yùn)行環(huán)境js引擎都會(huì)內(nèi)存 垃圾回收器;
- 垃圾回收器我們也會(huì)簡稱為GC,所以在很多地方你看到GC其實(shí)指的是垃圾回收器;
但是這里又出現(xiàn)了另外一個(gè)很關(guān)鍵的問題:GC怎么知道哪些對象是不再使用的呢?
- 這里就要用到GC的算法了
0.3.1 常見的GC算法 – 引用計(jì)數(shù)
引用計(jì)數(shù):
- 當(dāng)一個(gè)對象有一個(gè)引用指向它時(shí),那么這個(gè)對象的引用就+1,當(dāng)一個(gè)對象的引用為0時(shí),這個(gè)對象就可以被銷毀掉;
- 這個(gè)算法有一個(gè)很大的弊端就是會(huì)產(chǎn)生循環(huán)引用,比如如下:循環(huán)引用的兩個(gè)obj無法被GC回收
0.3.2 常見的GC算法 – 標(biāo)記清除
標(biāo)記清除:
- 這個(gè)算法是設(shè)置一個(gè)根對象(root object)(在瀏覽器中,這個(gè)根對象就是全局對象GO),垃圾回收器會(huì)定期從這個(gè)根開始,找所有從根開始有引用到的對象,對于哪些沒有引用到的對象,就認(rèn)為是不可用的對象;
- 這個(gè)算法可以很好的解決循環(huán)引用的問題;
JS引擎比較廣泛的采用的就是標(biāo)記清除算法,當(dāng)然類似于V8引擎為了進(jìn)行更好的優(yōu)化,它在算法的實(shí)現(xiàn)細(xì)節(jié)上也會(huì)結(jié)合一些其他的算法。
一、JS中閉包的定義
- 一個(gè)普通的函數(shù)function,如果它可以訪問外層作用于的自由變量,那么這個(gè)函數(shù)就是一個(gè)閉包;
- 從廣義的角度來說:JavaScript中的函數(shù)都是閉包;
- 從狹義的角度來說:JavaScript中一個(gè)函數(shù),如果訪問了外層作用于的變量,那么它是一個(gè)閉包;
二、閉包的訪問過程(內(nèi)存圖)
如果我們編寫了如下的代碼,它一定是形成了閉包的:
2.1 創(chuàng)建GO對象,創(chuàng)建全局執(zhí)行上下文并壓入執(zhí)行上下文棧中
2.2 執(zhí)行foo(),創(chuàng)建foo函數(shù)的AO對象
2.3 創(chuàng)建foo函數(shù)執(zhí)行上下文并壓入執(zhí)行上下文棧中,然后開始執(zhí)行foo函數(shù)內(nèi)的代碼
2.4 foo函數(shù)執(zhí)行結(jié)束,將foo函數(shù)執(zhí)行上下文彈出棧
2.5 執(zhí)行fn()函數(shù),創(chuàng)建fn函數(shù)(bar)的AO對象,創(chuàng)建fn函數(shù)(bar)的函數(shù)執(zhí)行上下文并壓入執(zhí)行上下文棧中
2.6 fn函數(shù)(bar)執(zhí)行完成,將fn函數(shù)(bar)的函數(shù)執(zhí)行上下文彈出棧,垃圾回收器銷毀bar函數(shù)的AO對象
注意:在此之前,是存在內(nèi)存泄漏的,因?yàn)閒oo函數(shù)執(zhí)行結(jié)束了,但是foo函數(shù)的AO對象并沒有被銷毀(因?yàn)橛衎ar函數(shù)對象指向它)。foo函數(shù)的AO對象是應(yīng)該被銷毀的但沒被銷毀,所以說存在內(nèi)存泄露!
2.7 執(zhí)行fn = null,由于fn變量指向null,bar函數(shù)的AO對象沒有任何變量指向,所以垃圾回收器會(huì)銷毀bar函數(shù)的AO對象
2.8 由于bar函數(shù)的AO對象被垃圾回收器銷毀,foo函數(shù)的AO對象沒有任何變量指向,也會(huì)被垃圾回收器銷毀
2.9 指向foo = null,foo變量指向null,不再指向foo函數(shù)對象
2.10 由于foo函數(shù)對象沒有任何變量指向,所以會(huì)被垃圾回收器銷毀
三、AO不使用的屬性
我們來研究一個(gè)問題:AO對象不會(huì)被銷毀時(shí),是否里面的所有屬性都不會(huì)被釋放?
- 下面這段代碼中name屬于閉包的父作用域里面的變量;
- 我們知道形成閉包之后count一定不會(huì)被銷毀掉,那么name是否會(huì)被銷毀掉呢?
- 這里我打上了斷點(diǎn),我們可以在瀏覽器上看看結(jié)果;
答案:AO不使用的屬性name是會(huì)被銷毀的
總結(jié)
以上是生活随笔為你收集整理的JS高级——内存管理和闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cookie与session原理详解
- 下一篇: JS数据结构与算法——选择排序(把小的数