一、內(nèi)存
1、內(nèi)存概述
在硬件層面但绕,計(jì)算機(jī)內(nèi)存是由大量觸發(fā)電路組成的。每個(gè)觸發(fā)電路包含了一些晶體管并且能夠存儲(chǔ)一個(gè)位(bit)惶看。
單個(gè)觸發(fā)器可通過唯一的標(biāo)識(shí)符尋址捏顺,因此我們可以讀取并重寫它們。
在概念上碳竟,我們可以將整個(gè)計(jì)算機(jī)內(nèi)存看作是我們可以讀和寫的一個(gè)由位組成的大數(shù)組草丧。
很多東西都存儲(chǔ)在內(nèi)存中:
1、所有程序使用的所有變量和數(shù)據(jù)莹桅;
2昌执、程序的代碼,包括操作系統(tǒng)的诈泼。
靜態(tài)和動(dòng)態(tài)內(nèi)存分配:
2懂拾、內(nèi)存生命周期
每種編程語(yǔ)言的內(nèi)存生命周期都是差不多的:分配內(nèi)存 => 使用內(nèi)存 => 釋放內(nèi)存
“使用內(nèi)存”在所有語(yǔ)言中都是顯性的,而“分配內(nèi)存”和“釋放內(nèi)存”在低級(jí)語(yǔ)言中是明確的铐达,在 JavaScript 等高級(jí)語(yǔ)言中大多是隱性的岖赋。
二、JavaScript 內(nèi)存分配概述
1瓮孙、概述
像 C 語(yǔ)言唐断,擁有底層原始的內(nèi)存管理方法,如 malloc() 杭抠、free()脸甘。
而 JavaScript,在創(chuàng)建(對(duì)象偏灿、字符串等)時(shí)分配內(nèi)存丹诀,并且在不再使用時(shí)自動(dòng)釋放。后面的過程被稱為垃圾回收。
這種自動(dòng)的垃圾回收就是管理混亂的根源铆遭,并造成了 JavaScript (其他高級(jí)語(yǔ)言)開發(fā)者不關(guān)心內(nèi)存管理的錯(cuò)誤現(xiàn)象硝桩。
即使是高級(jí)語(yǔ)言,自動(dòng)內(nèi)存管理也會(huì)遇到問題——垃圾回收中的錯(cuò)誤或性能等問題枚荣,因此碗脊,我們需要對(duì)內(nèi)存管理有一定的了解,以便處理內(nèi)存管理中遇到的問題棍弄。
2望薄、JavaScript 的回收機(jī)制
JavaScript垃圾回收的機(jī)制很簡(jiǎn)單:找出不再使用的變量疟游,然后釋放掉其占用的內(nèi)存呼畸,但是這個(gè)過程不是實(shí)時(shí)的,因?yàn)槠溟_銷比較大颁虐,所以垃圾回收系統(tǒng)(GC)會(huì)按照固定的時(shí)間間隔,周期性的執(zhí)行蛮原。
由于事實(shí)上發(fā)現(xiàn)內(nèi)存“不再被需要”是不可判定的,因此垃圾收集的通常解決方案都存在局限性另绩。
用于標(biāo)記的無(wú)用變量的策略可能因?qū)崿F(xiàn)而有所區(qū)別儒陨,通常情況下有兩種實(shí)現(xiàn)方式:標(biāo)記清除和引用計(jì)數(shù)。
1)引用計(jì)數(shù)
這是最簡(jiǎn)單的垃圾回收算法笋籽。如果一個(gè)對(duì)象指向它的引用數(shù)為 0蹦漠,那么它就應(yīng)該被“垃圾回收”了。
var o1 = {
o2: {
x: 1
}
};
// 2 objects are created. // 'o2' is referenced by 'o1' object as one of its properties.// None can be garbage-collected
var o3 = o1; // the 'o3' variable is the second thing that
// has a reference to the object pointed by 'o1'.
o1 = 1; // now, the object that was originally in 'o1' has a
// single reference, embodied by the 'o3' variable
var o4 = o3.o2; // reference to 'o2' property of the object.
// This object has now 2 references: one as
// a property.
// The other as the 'o4' variable
o3 = '374'; // The object that was originally in 'o1' has now zero
// references to it.
// It can be garbage-collected.
// However, what was its 'o2' property is still
// referenced by the 'o4' variable, so it cannot be
// freed.
o4 = null; // what was the 'o2' property of the object originally in
// 'o1' has zero references to it.
// It can be garbage collected.
但出現(xiàn)循環(huán)依賴就會(huì)產(chǎn)生限制车海。
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
上面代碼中笛园,兩個(gè)對(duì)象彼此引用,創(chuàng)建了一個(gè)循環(huán)侍芝。
在函數(shù)調(diào)用之后研铆,它們離開了作用域,因此它們實(shí)際上已經(jīng)無(wú)用了州叠,可以被釋放了棵红。然而,引用計(jì)數(shù)算法認(rèn)為咧栗,由于兩個(gè)對(duì)象中的每一個(gè)至少被引用了一次逆甜,所以也不能被垃圾回收。
2)標(biāo)記掃描算法
為了確定一個(gè)對(duì)象是否被需要致板,這個(gè)算法會(huì)確定對(duì)象是否可以訪問交煞。
該算法由以下步驟組成:
垃圾回收器構(gòu)建“roots”列表。Roots 通常是代碼中保留引用的全局變量可岂。在 JavaScript 中错敢,“window” 對(duì)象可以作為 root 全局變量示例。
所有的 roots 被檢查并標(biāo)記為 active(即不是垃圾)。所有的 children 也被遞歸檢查稚茅。從 root 能夠到達(dá)的一切都不被認(rèn)為是垃圾纸淮。
所有為被標(biāo)記為 active 的內(nèi)存可以被認(rèn)為是垃圾了。收集器限制可以釋放這些內(nèi)存并將其返回到操作系統(tǒng)亚享。
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
即使在循環(huán)情況下咽块,在函數(shù)調(diào)用后,兩個(gè)對(duì)象不再被從全局對(duì)象可訪問的東西所引用欺税。因此侈沪,垃圾回收器將發(fā)現(xiàn)它們是不可達(dá)的。
3晚凿、常見的 JavaScript 內(nèi)存泄漏
內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放亭罪,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果歼秽。
1)全局變量
JavaScript 正常模式下应役,對(duì)未聲明的變量的引用在全局對(duì)象內(nèi)創(chuàng)建一個(gè)新變量。即:
function foo(arg) {
bar = "some text";
}
等價(jià)于
function foo(arg) {
window.bar = "some text";
}
如果 bar 本應(yīng)是函數(shù) foo 內(nèi)的局部變量燥筷,而忘記聲明箩祥,就會(huì)意外地創(chuàng)建一個(gè)全局變量。這就產(chǎn)生了內(nèi)存泄漏肆氓。
下面代碼也會(huì)創(chuàng)建意外的全局變量:
function foo() {
this.var1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)// rather than being undefined.
foo();
為防止上述兩種創(chuàng)建意外全局變量的可能性袍祖,可以使用嚴(yán)格模式,在 JavaScript 代碼開頭添加“use strict”谢揪。嚴(yán)格模式中在上面兩種情況下會(huì)拋出錯(cuò)誤蕉陋。
由于全局無(wú)法自動(dòng)回收,除非將其賦值為 null 或重新進(jìn)行分配键耕。
那么在我們使用全局變量時(shí)要注意寺滚,尤其是用來臨時(shí)存儲(chǔ)和處理大量信息的全局變量非常值得關(guān)注。
如果你必須使用全局變量來存儲(chǔ)大量數(shù)據(jù)屈雄,請(qǐng)確保在使用完后村视,對(duì)其賦值為 null 或重新分配。
2)閉包
閉包可以維持函數(shù)內(nèi)局部變量酒奶,使其得不到釋放蚁孔。 上例定義事件回調(diào)時(shí),由于是函數(shù)內(nèi)定義函數(shù)惋嚎,并且內(nèi)部函數(shù)--事件回調(diào)的引用外暴了杠氢,形成了閉包:
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
}
解決之道,將事件處理函數(shù)定義在外部另伍,解除閉包,或者在定義事件處理函數(shù)的外部函數(shù)中鼻百,刪除對(duì)dom的引用:
//將事件處理函數(shù)定義在外部
function onclickHandler(){
//do something
}
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=onclickHandler;
}
//在定義事件處理函數(shù)的外部函數(shù)中绞旅,刪除對(duì)dom的引用
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
obj=null;
}
3) 被遺忘的定時(shí)器或回調(diào)
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //This will be executed every ~5 seconds.
如上,定時(shí)器可能會(huì)引用不再需要的節(jié)點(diǎn)或數(shù)據(jù)温艇,若 renderer 在將來被移除因悲,整個(gè)定時(shí)器模塊都不再被需要,但 interval handler 因?yàn)?interval 的存貨勺爱,所以無(wú)法被回收(要停止 interva晃琳,才能回收)。因此 serverData 可能存儲(chǔ)了大量數(shù)據(jù)琐鲁,不能被回收进鸠。
那么我們就應(yīng)在他們不再被需要的時(shí)候顯示地刪除它們(或讓相關(guān)對(duì)象變?yōu)椴豢蛇_(dá))慨灭。
var element = document.getElementById('launch-button');
var counter = 0;
function onClick(event) {
counter++;
element.innerHtml = 'text ' + counter;
}
element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers // that don't handle cycles well.
如今鳖擒,現(xiàn)代瀏覽器(包括 IE 和 Edge)都使用的是現(xiàn)代垃圾回收算法台腥,可以檢測(cè)這些循環(huán)依賴并正確的處理它們。換句話說蒜撮,讓一個(gè)節(jié)點(diǎn)不可達(dá)暴构,可以不必而在調(diào)用 removeEventListener跪呈。
框架和庫(kù)段磨,例如 jQuery ,在處理掉節(jié)點(diǎn)之前會(huì)刪除 listeners (使用它們特定的 API)耗绿。這些由庫(kù)的內(nèi)部進(jìn)了處理苹支,確保泄漏不會(huì)發(fā)生。即使是在有問題的瀏覽器下運(yùn)行误阻,如债蜜。。究反。寻定。IE6。
4精耐、怎樣避免內(nèi)存泄漏
1)減少不必要的全局變量狼速,或者生命周期較長(zhǎng)的對(duì)象,及時(shí)對(duì)無(wú)用的數(shù)據(jù)進(jìn)行垃圾回收卦停;
2)注意程序邏輯向胡,避免“死循環(huán)”之類的 ;
3)避免創(chuàng)建過多的對(duì)象 原則:不用了的東西要及時(shí)歸還惊完。
參考:
https://blog.csdn.net/michael8512/article/details/77888000
https://blog.csdn.net/tangxiaolang101/article/details/78113871
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management