一,垃圾回收釋義:
垃圾回收基本思路:確定那個變量不會再使用,然后釋放它占用的內存。這個過程是周期性的,即垃圾回收程序每隔一定時間就會自動運行。
垃圾回收程序必須跟蹤記錄那個變量還在使用,以及那個變量不會再使用,以便回收內存,否則,JavaScript的解釋器將會消耗完系統(tǒng)中所有可用的內存,造成系統(tǒng)崩潰;
var n = 'java';
var c = 'c++';
var n = c;//重寫n
console.log(n);//'c++'?
代碼運行之后,“java”這個字符串失去了引用,系統(tǒng)檢測到這個事實之后,就會釋放該字符串的存儲空間以便這些空間可以被再利用;
各大瀏覽器采用的垃圾回收有兩種方法:標記清理和引用計數(shù)
二,標記清理:
? ? 標記清理是JavaScript最常用的垃圾回收策略;
? ? 當變量進入上下文,比如在函數(shù)內部聲明一個變量時,這個變量會被加上存在于上下文中的標記,而在上下文中的變量,邏輯上講,永遠不應該釋放他們的內存,因為只要上下文中的代碼在運行,就有可能用到它們,當變量離開上下文時,也會被加上離開上下文的標記。
? 給變量加標記的方式有很多種,比如當變量進入上下文時,反轉某一位;或者可以維護“在上下文中”和“不在上下文中”兩個變量列表,可以把變量從一個列表轉移到另一個列表。
垃圾回收程序運行的時候,會標記內存中所有的變量(標記方法有多種),然后,它會將所有在上下文中的變量,以及被在上下文中的變量引用的變量的標記去掉,再此之后再被加上標記的變量就是待刪除了,原因是在任何上下文中的變量都訪問不到它們了,隨后垃圾回收程序走一次內存清理,銷毀帶標記的所有值并收回它們的內存。
三,引用計數(shù):
? ?另一種不太常見的垃圾回收策略是引用計數(shù)。引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)。當聲明了一個變量并將一個引用類型賦值給該變量時,則這個值的引用次數(shù)就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數(shù)就減1。當這個引用次數(shù)變成0時,則說明沒有辦法再訪問這個值了,因此 可以安全的收回其內存了。垃圾回收程序瑕疵運行的時候就會釋放引用數(shù)為0的值得內存。
?引用計數(shù)方法存在 循環(huán)引用的問題,就是對象A有一個指針指向對象B,而對象B也引用了對象A,例如:
function problem(){
? ? let objA = new Object();
? ? let objB = new Object();
? ? objA.someOtherObject = objB;
? ? objB.anOtherObject =objA;
}
?objA和objB通過各自的屬性相互引用;也就是說這兩個對象的引用次數(shù)都是2。在采用引用計數(shù)的策略中,由于函數(shù)執(zhí)行之后,這兩個對象都離開了作用域,函數(shù)執(zhí)行完成之后,objA和objB還將會繼續(xù)存在,因為他們的引用次數(shù)永遠不會是0。這樣的相互引用如果說很大量的存在就會導致大量的內存永遠不會被釋放。
IE中有一部分對象并不是原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object
Model,組件對象)對象的形式實現(xiàn)的,而COM對象的垃圾回收器就是采用的引用計數(shù)的策略。因此,即使IE的Javascript引擎使用標記清除的策略來實現(xiàn)的,但JavaScript訪問的COM對象依然是基于引用計數(shù)的策略的。只要IE中涉及COM對象,就會存在循環(huán)引用的問題;示例如下:
let element = document.getElementById('some_element');
let myObject = new Object();
myObject.element = element;
element.someObject = myObject
?面這個例子中,在一個DOM元素(element)與一個原生JavaScript對象(myObject)之間建立了循環(huán)引用。其中,變量myObject有一個名為element的屬性指向element;而變量element有一個名為someObject的屬性回指到myObject。由于循環(huán)引用,即使將例子中的DOM從頁面中移除,內存也永遠不會回收。
?為避免類似循環(huán)引用問題,在確保不使用的情況下切斷原生JavaScript對象與DOM元素之間的連接。通過以下代碼可以清楚前面例子中建立的循環(huán)引用:
myObject.element = null;
element.someObject = null;
四,內存管理:提升性能
? ?1,解除引用:
? ? ? ?將內存占用量保持在一個較小的值可以讓頁面性能更好,優(yōu)化內存占用的最佳手段就是保證在執(zhí)行代碼時只保存必要的數(shù)據(jù),如果數(shù)據(jù)不再必要,那么把它設置為null,從而釋放其引用。
? ? ? ?適合全局變量和全局對象的屬性。局部變量在超出作用域后會被自動解除引用。
? ? ? 示例如下:
function cperson(name){
? ? let locPerson = new Object();
? ? locPerson.name = name;
? ? return locPerson;
}
let globalPerson = cperson('java');
console.log(globalPerson.name)//'java'
globalPerson = null;//解除globalPerson對值得引用
? ?代碼中,變量globalPerson保存著cperson()函數(shù)條用返回的值,在cperson()內部,locPerson創(chuàng)建了一個對比并給他添加了一個name屬性,然后locPerson作為函數(shù)值被返回,并賦值給globalPerson。locPerson在cperson()內部執(zhí)行完成超出上下文后會自動被解除引用。不需要顯示處理,但globalPerson是一個全局變量,應該在不再需要時手動解除其引用(globalPerson?=?null);
注意:解除對一個值得一弄并不會自動導致行管內存被回收,解除引用的關鍵在于確保相關的值已經(jīng)不再上下文里了,因此它在下次垃圾回收時會被回收。
2,使用const 和 let 聲明提升性能:
? ?const 和 let都是以塊為作用域,相比使用var,使用這兩個新關鍵字可能會更早的讓垃圾回收程序接入,盡早回收應該回收的內存。在塊作用域比函數(shù)作用域更早終止的情況下,這就有可能發(fā)生。
3,隱藏類和刪除操作:
? ? ? 根據(jù)JavaScript坐在的運行環(huán)境,有時候需要根據(jù)瀏覽器使用JavaScript引擎來采取不同的性能優(yōu)化策略。比如 Chrome的 V8JavaScript引擎,V8將在解釋后的JavaScript代碼編譯為實際的機器代碼時會利用“隱藏類”;運行期間,V8會將創(chuàng)建的對象與隱藏類關聯(lián)起來,以跟蹤他們的屬性特征。
? 如下示例:V8會在后臺配置,讓這兩個類實例共享相同的隱藏類,因為這兩個實例共享同一個構造函數(shù)和原型;
function Cperson(){
? ? this.name = 'java script';
}
let n1 = new Cperson();
let n2 = new Cperson();
避免JavaScript的“先創(chuàng)建再補充”式的動態(tài)屬性賦值,并在構造函數(shù)中一次性聲明所有屬性。
? 示例如下:下面代碼在不考慮 hasOwnProperty的返回值,可以共享一個隱藏類,從而帶來潛在的性能提升。
function Cperson(sex){
? ? this.name = 'java script';
? ? this.person = sex;
}
let n1 = new Cperson();
let n2 = new Cperson('man');
? ? ?最佳方案是把不想要的屬性設置為null,這樣可以保持隱藏類不變和繼續(xù)共享,同時也能達到刪除引用值供垃圾回收的效果:如下示例:
function Cperson(){
? ? this.name = 'java script';
? ? this.person = 'php hello';
}
let n1 = new Cperson();
let n2 = new Cperson();
n1.person = null;
4,內存泄漏:
? ? ? ? ?內存泄漏大部分是由不合理的引用導致的;
? >> 意外聲明全局變量師最常見的內存泄漏問題,
? ? ? ? ?如下代碼示例:
function evetPerson(){
? ? name = 'java';
}
? ? ?解釋器會把變量name當做window的屬性來創(chuàng)建(相當于window.name = ‘java’)。在window對象上創(chuàng)建屬性,只要window對象本身不被清理就不會消失,只要在變量聲明前加上 var? let 或者 const關鍵字即可。這樣變量就會在函數(shù)執(zhí)行完畢后離開作用域。
?>> 定時器也可能會導致內存泄漏。如下代碼定時器的回調通過閉包引用了外部變量:
let name = 'java';
setInterval(()=>{
? ? console.log(name);
},100)
? 只要定時器一直運行,回調函數(shù)中引用的name就會一直占用內存,垃圾回收程序就不會清理外部去變量。
??>> 使用JavaScipt閉包很容易在不知不覺間造成內存泄漏:
? ? ?如下示例:
let outer = function(){
? ? let name = 'java';
? ? return function(){
? ? ? ? return name;
? ? }
}
? 調用outer()會導致分配給name的內存被泄漏,以上代碼創(chuàng)建了一個內部閉包,只要返回的函數(shù)存在就不能清理name,閉包一直在引用著它。