您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何管理JS中的內(nèi)存”,在日常操作中,相信很多人在如何管理JS中的內(nèi)存問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何管理JS中的內(nèi)存”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
JS 環(huán)境中分配的內(nèi)存有如下聲明周期:
內(nèi)存分配:當(dāng)我們申明變量、函數(shù)、對象的時候,系統(tǒng)會自動為他們分配內(nèi)存
內(nèi)存使用:即讀寫內(nèi)存,也就是使用變量、函數(shù)等
內(nèi)存回收:使用完畢,由垃圾回收機制自動回收不再使用的內(nèi)存
JS 的內(nèi)存分配
為了不讓程序員費心分配內(nèi)存,JavaScript 在定義變量時就完成了內(nèi)存分配。
有些函數(shù)調(diào)用結(jié)果是分配對象內(nèi)存:
var d = new Date(); // 分配一個 Date 對象 var e = document.createElement('div'); // 分配一個 DOM 元素
有些方法分配新變量或者新對象:
var s = "azerty"; var s2 = s.substr(0, 3); // s2 是一個新的字符串 // 因為字符串是不變量, // JavaScript 可能決定不分配內(nèi)存, // 只是存儲了 [0-3] 的范圍。 var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // 新數(shù)組有四個元素,是 a 連接 a2 的結(jié)果
JS 的內(nèi)存使用
使用值的過程實際上是對分配內(nèi)存進行讀取與寫入的操作。 讀取與寫入可能是寫入一個變量或者一個對象的屬性值,甚至傳遞函數(shù)的參數(shù)。
var a = 10; // 分配內(nèi)存 console.log(a); // 對內(nèi)存的使用
JS 的內(nèi)存回收
JS 有自動垃圾回收機制,那么這個自動垃圾回收機制的原理是什么呢? 其實很簡單,就是找出那些不再繼續(xù)使用的值,然后釋放其占用的內(nèi)存。
大多數(shù)內(nèi)存管理的問題都在這個階段。 在這里最艱難的任務(wù)是找到不再需要使用的變量。
不再需要使用的變量也就是生命周期結(jié)束的變量,是局部變量,局部變量只在函數(shù)的執(zhí)行過程中存在, 當(dāng)函數(shù)運行結(jié)束,沒有其他引用(閉包),那么該變量會被標(biāo)記回收。
全局變量的生命周期直至瀏覽器卸載頁面才會結(jié)束,也就是說全局變量不會被當(dāng)成垃圾回收。
因為自動垃圾回收機制的存在,開發(fā)人員可以不關(guān)心也不注意內(nèi)存釋放的有關(guān)問題,但對無用內(nèi)存的釋放這件事是客觀存在的。 不幸的是,即使不考慮垃圾回收對性能的影響,目前***的垃圾回收算法,也無法智能回收所有的極端情況。
接下來我們來探究一下 JS 垃圾回收的機制。
引用
垃圾回收算法主要依賴于引用的概念。
在內(nèi)存管理的環(huán)境中,一個對象如果有訪問另一個對象的權(quán)限(隱式或者顯式),叫做一個對象引用另一個對象。
例如,一個Javascript對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。
在這里,“對象”的概念不僅特指 JavaScript 對象,還包括函數(shù)作用域(或者全局詞法作用域)。
引用計數(shù)垃圾收集
這是最初級的垃圾回收算法。
引用計數(shù)算法定義“內(nèi)存不再使用”的標(biāo)準(zhǔn)很簡單,就是看一個對象是否有指向它的引用。 如果沒有其他對象指向它了,說明該對象已經(jīng)不再需了。
由上面可以看出,引用計數(shù)算法是個簡單有效的算法。但它卻存在一個致命的問題:循環(huán)引用。
如果兩個對象相互引用,盡管他們已不再使用,垃圾回收不會進行回收,導(dǎo)致內(nèi)存泄露。
來看一個循環(huán)引用的例子:
上面我們申明了一個函數(shù) f ,其中包含兩個相互引用的對象。 在調(diào)用函數(shù)結(jié)束后,對象 o1 和 o2 實際上已離開函數(shù)范圍,因此不再需要了。 但根據(jù)引用計數(shù)的原則,他們之間的相互引用依然存在,因此這部分內(nèi)存不會被回收,內(nèi)存泄露不可避免了。
再來看一個實際的例子:
var div = document.createElement("div");
div.onclick = function() {
console.log("click");
};
上面這種JS寫法再普通不過了,創(chuàng)建一個DOM元素并綁定一個點擊事件。 此時變量 div 有事件處理函數(shù)的引用,同時事件處理函數(shù)也有div的引用?。╠iv變量可在函數(shù)內(nèi)被訪問)。 一個循序引用出現(xiàn)了,按上面所講的算法,該部分內(nèi)存無可避免的泄露了。
為了解決循環(huán)引用造成的問題,現(xiàn)代瀏覽器通過使用標(biāo)記清除算法來實現(xiàn)垃圾回收。
標(biāo)記清除算法
標(biāo)記清除算法將“不再使用的對象”定義為“無法達(dá)到的對象”。 簡單來說,就是從根部(在JS中就是全局對象)出發(fā)定時掃描內(nèi)存中的對象。 凡是能從根部到達(dá)的對象,都是還需要使用的。 那些無法由根部出發(fā)觸及到的對象被標(biāo)記為不再使用,稍后進行回收。
從這個概念可以看出,無法觸及的對象包含了沒有引用的對象這個概念(沒有任何引用的對象也是無法觸及的對象)。 但反之未必成立。
工作流程:
垃圾收集器會在運行的時候會給存儲在內(nèi)存中的所有變量都加上標(biāo)記。
從根部出發(fā)將能觸及到的對象的標(biāo)記清除。
那些還存在標(biāo)記的變量被視為準(zhǔn)備刪除的變量。
***垃圾收集器會執(zhí)行***一步內(nèi)存清除的工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
循環(huán)引用不再是問題了
再看之前循環(huán)引用的例子:
function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty"; } f();
函數(shù)調(diào)用返回之后,兩個循環(huán)引用的對象在垃圾收集時從全局對象出發(fā)無法再獲取他們的引用。 因此,他們將會被垃圾回收器回收。
內(nèi)存泄漏
什么是內(nèi)存泄漏
程序的運行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運行時(runtime)就必須供給內(nèi)存。
對于持續(xù)運行的服務(wù)進程(daemon),必須及時釋放不再用到的內(nèi)存。 否則,內(nèi)存占用越來越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進程崩潰。
本質(zhì)上講,內(nèi)存泄漏就是由于疏忽或錯誤造成程序未能釋放那些已經(jīng)不再使用的內(nèi)存,造成內(nèi)存的浪費。
內(nèi)存泄漏的識別方法
經(jīng)驗法則是,如果連續(xù)五次垃圾回收之后,內(nèi)存占用一次比一次大,就有內(nèi)存泄漏。 這就要求實時查看內(nèi)存的占用情況。
在 Chrome 瀏覽器中,我們可以這樣查看內(nèi)存占用情況
打開開發(fā)者工具,選擇 Performance 面板
在頂部勾選 Memory
點擊左上角的 record 按鈕
在頁面上進行各種操作,模擬用戶的使用情況
一段時間后,點擊對話框的 stop 按鈕,面板上就會顯示這段時間的內(nèi)存占用情況
來看一張效果圖:
我們有兩種方式來判定當(dāng)前是否有內(nèi)存泄漏:
多次快照后,比較每次快照中內(nèi)存的占用情況,如果呈上升趨勢,那么可以認(rèn)為存在內(nèi)存泄漏
某次快照后,看當(dāng)前內(nèi)存占用的趨勢圖,如果走勢不平穩(wěn),呈上升趨勢,那么可以認(rèn)為存在內(nèi)存泄漏
在服務(wù)器環(huán)境中使用 Node 提供的 process.memoryUsage 方法查看內(nèi)存情況
console.log(process.memoryUsage()); // { // rss: 27709440, // heapTotal: 5685248, // heapUsed: 3449392, // external: 8772 // }
process.memoryUsage返回一個對象,包含了 Node 進程的內(nèi)存占用信息。
該對象包含四個字段,單位是字節(jié),含義如下:
rss(resident set size):所有內(nèi)存占用,包括指令區(qū)和堆棧。
heapTotal:"堆"占用的內(nèi)存,包括用到的和沒用到的。
heapUsed:用到的堆的部分。
external: V8 引擎內(nèi)部的 C++ 對象占用的內(nèi)存。
判斷內(nèi)存泄漏,以heapUsed字段為準(zhǔn)。
意外的全局變量
function foo() { bar1 = 'some text'; // 沒有聲明變量 實際上是全局變量 => window.bar1 this.bar2 = 'some text' // 全局變量 => window.bar2 } foo();
在這個例子中,意外的創(chuàng)建了兩個全局變量 bar1 和 bar2
被遺忘的定時器和回調(diào)函數(shù)
在很多庫中, 如果使用了觀察者模式, 都會提供回調(diào)方法, 來調(diào)用一些回調(diào)函數(shù)。 要記得回收這些回調(diào)函數(shù)。舉一個 setInterval的例子:
var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); // 每 5 秒調(diào)用一次
如果后續(xù) renderer 元素被移除,整個定時器實際上沒有任何作用。 但如果你沒有回收定時器,整個定時器依然有效, 不但定時器無法被內(nèi)存回收, 定時器函數(shù)中的依賴也無法回收。在這個案例中的 serverData 也無法被回收。
閉包
在 JS 開發(fā)中,我們會經(jīng)常用到閉包,一個內(nèi)部函數(shù),有權(quán)訪問包含其的外部函數(shù)中的變量。 下面這種情況下,閉包也會造成內(nèi)存泄露:
這段代碼,每次調(diào)用 replaceThing 時,theThing 獲得了包含一個巨大的數(shù)組和一個對于新閉包 someMethod 的對象。 同時 unused 是一個引用了 originalThing 的閉包。
這個范例的關(guān)鍵在于,閉包之間是共享作用域的,盡管 unused 可能一直沒有被調(diào)用,但是 someMethod 可能會被調(diào)用,就會導(dǎo)致無法對其內(nèi)存進行回收。 當(dāng)這段代碼被反復(fù)執(zhí)行時,內(nèi)存會持續(xù)增長。
DOM 引用
很多時候, 我們對 Dom 的操作, 會把 Dom 的引用保存在一個數(shù)組或者 Map 中。
上述案例中,即使我們對于 image 元素進行了移除,但是仍然有對 image 元素的引用,依然無法對齊進行內(nèi)存回收。
另外需要注意的一個點是,對于一個 Dom 樹的葉子節(jié)點的引用。 舉個例子: 如果我們引用了一個表格中的td元素,一旦在 Dom 中刪除了整個表格,我們直觀的覺得內(nèi)存回收應(yīng)該回收除了被引用的 td 外的其他元素。 但是事實上,這個 td 元素是整個表格的一個子元素,并保留對于其父元素的引用。 這就會導(dǎo)致對于整個表格,都無法進行內(nèi)存回收。所以我們要小心處理對于 Dom 元素的引用。
記住一個原則:不用的東西,及時歸還。
減少不必要的全局變量,使用嚴(yán)格模式避免意外創(chuàng)建全局變量。
在你使用完數(shù)據(jù)后,及時解除引用(閉包中的變量,dom引用,定時器清除)。
組織好你的邏輯,避免死循環(huán)等造成瀏覽器卡頓,崩潰的問題。
到此,關(guān)于“如何管理JS中的內(nèi)存”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。