垃圾回收算法主要依賴引用的概念,例如一個對象若是有另一個對象的訪問權限,這裏就叫作一個對象引用另一個對象,不論這裏是顯式仍是隱式程序員
Js具備自動垃圾回收機制。垃圾收集器會按照固定的時間間隔週期性的執行。第二種程序員本身釋放算法
這個算最簡單的回收算法,大體是某地對象當沒有引用指向它的時候,也就是零指向,這個對象就會被垃圾回收機制回收瀏覽器
let arr = [1,2,3] arr = null // [1,2,3]沒有被引用,會被自動回收
工做原理:當變量進入環境時,將這個變量標記爲「進入環境」。當變量離開環境時,則將其標記爲「離開環境」。標記「離開環境」的就回收內存。緩存
工做流程:數據結構
垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記。閉包
去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。app
依然被標記的會被視爲準備刪除的變量。dom
垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收他們所佔用的內存空間。ide
工做原理:跟蹤記錄每一個值被引用的次數函數
工做流程:
聲明瞭一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1。
同一個值又被賦值給另外一個變量,這個引用類型值的引用次數加1.
當包含這個引用類型值的變量又被賦值成另外一個值了,那麼這個引用類型值的引用次數減1.
當引用次數變成0時,說明沒辦法訪問這個值了。
當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所佔的內存。
問題:循環引用 function f() { var o1 = {} var o2 = {} o1.p = o2; // o1 引用o2 o2.p = o1; // o2引用o1,這裏造成了一個循環引用 }
f()
objA指向內存中的引用類型,而這個引用類型的一個值又指向了另外一個引用類型,這樣,每一個引用類型的引用次數都是2,且在引用類型之間造成了循環引用,這樣,即便problem()函數執行完畢,把後期再也不使用的局部變量objA和objB釋放,可是由於引用類型的引用次數仍是1,那麼這兩個引用類型仍是不能被釋放的,這就形成了內存泄露。
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指向全局
緣由:全局變量,不會被回收。 js中的全局變量,只有當頁面被關閉後纔會銷燬
解決:使用嚴格模式避免,函數內使用var定義,塊內使用let、const。
2. 閉包引發的內存泄漏
緣由:閉包能夠維持函數內局部變量,使其得不到釋放。
function do(){ let thing = 'eat' return function(){ console.log(thing) }}
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用
三、 刪除元素形成的內存泄露。
<div id="wrap"> <span id="link">點擊</a> </div> <script> let wrap = document.querySelector('#wrap'), link = document.querySelector('#link'); function handleClick() { alert('clicked'); } link.addEventListener('click', handleClick ); wrap.removeChild(link); document.body.appendChild(link);
</sript>
即便link已經被移除了,而後咱們經過appendChild添加到div平級的地方,而後點擊以後仍是有事件發生的,說明這裏元素被移除而後添加,事件仍是能夠用的。
可是,咱們已經將之移除了,因此,後面就不須要了,可是span標籤仍是被link變量所引用,這樣,就形成了內存泄露。
因此,咱們能夠在link被移除的時候,就清除這個引用,以下所示:
<div id="wrap"> <span id="link">點擊</a> </div> <script> let wrap = document.querySelector('#wrap'), link = document.querySelector('#link') function handleClick() { alert('clicked'); } link.addEventListener('click', handleClick ); wrap.removeChild(link); link = null
緣由:雖然DOM刪除了,可是對象中還存在對dom的引用
解決:手動刪除。
被遺忘的定時器或者回調
var serverData = loadData();setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); }}, 5000);
緣由:定時器中有dom的引用,即便dom刪除了,可是定時器還在,因此內存中仍是有這個dom。 ** 解決**:手動刪除定時器和dom。
子元素存在引用引發的內存泄漏
緣由:div中的ul li , 獲得這個div,會間接引用某個獲得的li,那麼此時由於div間接引用li,即便li被清空,也仍是在內存中,而且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空
基本類型的值存在內存中,被保存在棧內存中。從一個變量向另外一個變量複製基本類型的值,會建立這個值的一個副本。
引用類型的值是對象,保存在堆內存中。
包含引用類型值的變量實際上包含的並非對象自己,而是一個指向該對象的指針。從一個變量向另外一個變量複製引用類型的值,複製的實際上是指針,所以兩個變量最終都指向同一個對象。
js不容許直接訪問內存中的位置,也就是不能直接訪問操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。
一、棧(操做系統):由操做系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧;
二、堆(操做系統): 通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收,分配方式卻是相似於鏈表。
一、棧使用的是一級緩存, 他們一般都是被調用時處於存儲空間中,調用完畢當即釋放;
二、堆是存放在二級緩存中,生命週期由虛擬機的垃圾回收算法來決定(並非一旦成爲孤兒對象就能被回收)。因此調用這些對象的速度要相對來得低一些。
堆(數據結構):堆能夠被當作是一棵樹,如:堆排序; 棧(數據結構):一種先進後出的數據結構。
Js具備自動垃圾回收機制。垃圾收集器會按照固定的時間間隔週期性的執行。第二種程序員本身釋放
這個算最簡單的回收算法,大體是某地對象當沒有引用指向它的時候,也就是零指向,這個對象就會被垃圾回收機制回收
let arr = [1,2,3]
arr = null // [1,2,3]沒有被引用,會被自動回收
工做原理:當變量進入環境時,將這個變量標記爲「進入環境」。當變量離開環境時,則將其標記爲「離開環境」。標記「離開環境」的就回收內存。
工做流程:
垃圾回收器在運行的時候會給存儲在內存中的全部變量都加上標記。
去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。
依然被標記的會被視爲準備刪除的變量。
垃圾回收器完成內存清除工做,銷燬那些帶標記的值並回收他們所佔用的內存空間。
工做原理:跟蹤記錄每一個值被引用的次數
工做流程:
聲明瞭一個變量並將一個引用類型的值賦值給這個變量,這個引用類型值的引用次數就是1。
同一個值又被賦值給另外一個變量,這個引用類型值的引用次數加1.
當包含這個引用類型值的變量又被賦值成另外一個值了,那麼這個引用類型值的引用次數減1.
當引用次數變成0時,說明沒辦法訪問這個值了。
當垃圾收集器下一次運行時,它就會釋放引用次數是0的值所佔的內存。
問題:循環引用
function f() {
var o1 = {}
var o2 = {}
o1.p = o2; // o1 引用o2
o2.p = o1; // o2引用o1,這裏造成了一個循環引用
}
f()
型,而這個引用類型的一個值又指向了另外一個引用類型,這樣,每一個引用類型的引用次數都是2,且在引用類型之間造成了循環引用,這樣,即便problem()函數執行完畢,把後期再也不使用的局部變量objA和objB釋放,可是由於引用類型的引用次數仍是1,那麼這兩個引用類型仍是不能被釋放的,這就形成了內存泄露。
· 循環引用其實是在堆中的兩個引用類型之間的循環引用,如右邊的兩個箭頭就構成了一個循環。
· 由於IE中的BOM、DOM的實現使用了COM,IE的JavaScript引擎使用的是標記清除的策略,可是JavaScript訪問的COM對象依然是基於引用計數的策略的。因此會存在循環引用的問題
本質上來講,內存泄漏就是不被須要的內存,由於某種緣由,沒有被回收或者釋放
使用了內存以後, 若是後面他們不會再被用到,可是尚未及時釋放,這就叫作內存泄露(memory leak)。若是出現了內存泄露,那麼有可能使得內存愈來愈大,而致使瀏覽器崩潰
1. 意外的全局變量引發的內存泄漏。
function foo(arg) {
bar = "this is a hidden global variable"; // 沒有用var }
function foo() {
this.variable = "potential accidental global";
}
// 通常函數調用,this指向全局
緣由:全局變量,不會被回收。 js中的全局變量,只有當頁面被關閉後纔會銷燬
解決:使用嚴格模式避免,函數內使用var定義,塊內使用let、const。
2. 閉包引發的內存泄漏
緣由:閉包能夠維持函數內局部變量,使其得不到釋放。
function do(){
let thing = 'eat'
return function(){
console.log(thing)
}}
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用
三、 刪除元素形成的內存泄露。
<div id="wrap">
<span id="link">點擊</a>
</div>
<script>
let wrap = document.querySelector('#wrap'),
link = document.querySelector('#link');
function handleClick() {
alert('clicked');
}
link.addEventListener('click', handleClick );
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.querySelector('#wrap'),
link = document.querySelector('#link')
function handleClick() {
alert('clicked');
}
link.addEventListener('click', handleClick );
wrap.removeChild(link);
link = null
緣由:雖然DOM刪除了,可是對象中還存在對dom的引用
解決:手動刪除。
被遺忘的定時器或者回調
var serverData = loadData();setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}}, 5000);
緣由:定時器中有dom的引用,即便dom刪除了,可是定時器還在,因此內存中仍是有這個dom。 ** 解決**:手動刪除定時器和dom。
子元素存在引用引發的內存泄漏
緣由:div中的ul li , 獲得這個div,會間接引用某個獲得的li,那麼此時由於div間接引用li,即便li被清空,也仍是在內存中,而且只要li不被刪除,他的父元素都不會被刪除。 ** 解決**:手動刪除清空