引用:https://blog.csdn.net/yingzizizizizizzz/article/details/77333996
https://www.cnblogs.com/zhuzhenwei918/p/7586240.html
垃圾回收機制
Js具有自動垃圾回收機制。垃圾收集器會按照固定的時間間隔周期性的執行。
標記清除
工作原理:是當變量進入環境時,將這個變量標記為“進入環境”。當變量離開環境時,則將其標記為“離開環境”。標記“離開環境”的就回收內存。
工作流程:
-
垃圾回收器在運行的時候會給存儲在內存中的所有變量都加上標記。
-
去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。
-
依然被標記的會被視為准備刪除的變量。
-
垃圾回收器完成內存清除工作,銷毀那些帶標記的值並回收他們所占用的內存空間。
引用計數
工作原理:跟蹤記錄每個值被引用的次數。
工作流程:
-
聲明了一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1。
-
同一個值又被賦值給另一個變量,這個引用類型值的引用次數加1.
-
當包含這個引用類型值的變量又被賦值成另一個值了,那么這個引用類型值的引用次數減1.
-
當引用次數變成0時,說明沒辦法訪問這個值了。
-
當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所占的內存。
問題:循環引用
function problem() { var objA = new Object(); var objB = new Object(); objA.someOtherObject = objB; objB.anotherObject = objA; }
objA指向內存中的引用類型,而這個引用類型的一個值又指向了另一個引用類型,這樣,每個引用類型的引用次數都是2,且在引用類型之間形成了循環引用,這樣,即使problem()函數執行完畢,把后期不再使用的局部變量objA和objB釋放,但是因為引用類型的引用次數還是1,那么這兩個引用類型還是不能被釋放的,這就造成了內存泄露。
- 循環引用實際上是在堆中的兩個引用類型之間的循環引用,如右邊的兩個箭頭就構成了一個循環。
- 因為IE中的BOM、DOM的實現使用了COM,IE的JavaScript引擎使用的是標記清除的策略,但是JavaScript訪問的COM對象依然是基於引用計數的策略的。所以會存在循環引用的問題。
var element = document.getElementById("some_element"); var myObj =new Object(); myObj.element = element; element.someObject = myObj;
這個例子中,一個DOM元素和一個原生JavaScript對象之間建立了循環引用。這樣,即使例子中的DOM從頁面中移除,內存也不會被回收
解決方法:以手動切斷他們的循環引用:
myObj.element = null; element.someObject =null;
內存泄漏
使用了內存之后, 如果后面他們不會再被用到,但是還沒有及時釋放,這就叫做內存泄露(memory leak)。如果出現了內存泄露,那么有可能使得內存越來越大,而導致瀏覽器崩潰。
引起內存泄漏的情況
1. 意外的全局變量引起的內存泄漏。
function foo(arg) { bar = "this is a hidden global variable"; // 沒有用var } function foo() { this.variable = "potential accidental global"; } // 一般函數調用,this指向全局
原因:全局變量,不會被回收。
解決:使用嚴格模式避免,函數內使用var定義,塊內使用let、const。
2. 閉包引起的內存泄漏
原因:閉包可以維持函數內局部變量,使其得不到釋放。
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。
3、 刪除元素造成的內存泄露。
<div id="wrap"> <span id="link">點擊</a> </div> <script> let wrap = document.getElementById('wrap'); link = document.getElementById('link'); function handleClick() { alert('clicked'); } link.addEventListener('click', handleClick ,false); wrap.removeChild(link); document.body.appendChild(link);
即使link已經被移除了,然后我們通過appendChild添加到div平級的地方,然后點擊之后還是有事件發生的,說明這里元素被移除然后添加,事件還是可以用的。
但是,我們已經將之移除了,所以,后面就不需要了,但是span標簽還是被link變量所引用,這樣,就造成了內存泄露。
所以,我們可以在link被移除的時候,就清除這個引用,如下所示:
<div id="wrap"> <span id="link">點擊</a> </div> <script> let wrap = document.getElementById('wrap'); link = document.getElementById('link'); function handleClick() { alert('clicked'); } link.addEventListener('click', handleClick ,false); wrap.removeChild(link); link = null;
原因:雖然DOM刪除了,但是對象中還存在對dom的引用
解決:手動刪除。
4. 被遺忘的定時器或者回調
原因:定時器中有dom的引用,即使dom刪除了,但是定時器還在,所以內存中還是有這個dom。 ** 解決**:手動刪除定時器和dom。
5. 子元素存在引用引起的內存泄漏
原因:div中的ul li , 得到這個div,會間接引用某個得到的li,那么此時因為div間接引用li,即使li被清空,也還是在內存中,並且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空。
堆內存和棧內存
基本類型是:Undefined/Null/Boolean/Number/String,
基本類型的值存在內存中,被保存在棧內存中。從一個變量向另一個變量復制基本類型的值,會創建這個值的一個副本。
引用類型:object
引用類型的值是對象,保存在堆內存中。
-
包含引用類型值的變量實際上包含的並不是對象本身,而是一個指向該對象的指針。從一個變量向另一個變量復制引用類型的值,復制的其實是指針,因此兩個變量最終都指向同一個對象。
-
js不允許直接訪問內存中的位置,也就是不能直接訪問操作對象的內存空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。
堆和棧的區別
一、堆棧空間分配區別:
1、棧(操作系統):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧;
2、堆(操作系統): 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似於鏈表。
二、堆棧緩存方式區別:
1、棧使用的是一級緩存, 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放;
2、堆是存放在二級緩存中,生命周期由虛擬機的垃圾回收算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
三、堆棧數據結構區別:
堆(數據結構):堆可以被看成是一棵樹,如:堆排序; 棧(數據結構):一種先進后出的數據結構。