摘要:本系列的第一篇文章著重提供一個關(guān)于引擎運行時和調(diào)用棧的概述。在硬件層面,計算機內(nèi)存由大量的觸發(fā)器組成。每個觸發(fā)器包含幾個晶體管能夠存儲一個比特譯注位??梢酝ㄟ^唯一標(biāo)識符來訪問單個觸發(fā)器,所以可以對它們進行讀寫操作。比特稱為個字節(jié)。
原文 How JavaScript works: memory management + how to handle 4 common memory leaks
幾周前我們開始了一個系列博文旨在深入挖掘 JavaScript 并弄清楚它的工作原理:我們認為通過了解 JavaScript 的構(gòu)建單元并熟悉它們是怎樣結(jié)合起來的,有助于寫出更好的代碼和應(yīng)用。
本系列的第一篇文章著重提供一個關(guān)于引擎、運行時和調(diào)用棧的概述。第二篇文章深入分析了 Google 的 V8 引擎的內(nèi)部實現(xiàn)并提供了一些編寫更優(yōu)質(zhì) JavaScript 代碼的建議。
在第三篇的本文中,我們將會討論另一個非常重要的主題,由于日常使用的編程語言的逐漸成熟和復(fù)雜性,它被越來越多的開發(fā)者忽視——內(nèi)存管理。我們還會提供一些在 SessionStack 中遵循的關(guān)于如何處理 JavaScript 內(nèi)存泄露的方法,我們必須保證 SessionStack 不會發(fā)生內(nèi)存泄漏,或?qū)е抡线M來的應(yīng)用增加內(nèi)存消耗。
概述像 C 這樣的語言,具有低水平的內(nèi)存管理原語如 malloc() 和 free(),這些原語被開發(fā)者用來顯式地向操作系統(tǒng)分配和釋放內(nèi)存。
同時,JavaScript 在事物(對象、字符串等)被創(chuàng)建時分配內(nèi)存,并在它們不再需要用到時自動釋放內(nèi)存,這個過程稱為垃圾收集。這個看似自動釋放資源的特性是困惑的來源,造成 JavaScript(和其他高級語言)開發(fā)者錯誤的印象,認為他們可以選擇不必關(guān)心內(nèi)存管理。這是個天大的誤解。
即便在使用高級編程語言時,開發(fā)者也應(yīng)該了解內(nèi)存管理(至少最基本的)。有時會遇到自動內(nèi)存管理的問題(如垃圾收集器的BUG和實現(xiàn)限制等),開發(fā)者應(yīng)該了解這些問題才能合理地處理它們(或找到適當(dāng)?shù)慕鉀Q方案,用最小的代價和代碼債)。
內(nèi)存生命周期無論使用哪種編程語言,內(nèi)存的生命周期幾乎總是相同的:
下面是周期中每個步驟發(fā)生了什么的概覽:
分配內(nèi)存——內(nèi)存由允許程序使用的操作系統(tǒng)分配。在低級編程語言(如 C)中這是一個作為開發(fā)人員應(yīng)該處理的顯式操作。而在高級編程語言中是由語言本身幫你處理的。
使用內(nèi)存——這是程序?qū)嶋H上使用之前所分配內(nèi)存的階段。讀寫操作發(fā)生在使用代碼中分配的變量時。
釋放內(nèi)存——現(xiàn)在是釋放不需要的整個內(nèi)存的時候了,這樣它才能變得空閑以便再次可用。與分配內(nèi)存一樣,在低級編程語言中這是一個顯式操作。
想要快速瀏覽調(diào)用棧和內(nèi)存堆的概念,可以閱讀我們關(guān)于這個主題的第一篇文章。
什么是內(nèi)存?在直接介紹 JavaScript 中的內(nèi)存之前,我們會簡要討論一下內(nèi)存是什么及它是怎樣工作的。
在硬件層面,計算機內(nèi)存由大量的觸發(fā)器組成。每個觸發(fā)器包含幾個晶體管能夠存儲一個比特(譯注:1位)??梢酝ㄟ^唯一標(biāo)識符來訪問單個觸發(fā)器,所以可以對它們進行讀寫操作。因此從概念上,我們可以把整個計算機內(nèi)存想象成一個巨大的可讀寫的比特陣列。
作為人類,我們并不擅長使用字節(jié)進行所有的思考和算術(shù),我們把它們組織成更大的組合,一起用來表示數(shù)字。8比特稱為1個字節(jié)。除字節(jié)之外,還有其他詞(有時是16比特、有時是32比特)。
很多東西存儲在內(nèi)存中:
所有程序使用的所有變量和其他數(shù)據(jù)。
程序代碼,包括操作系統(tǒng)的。
編譯器和操作系統(tǒng)一起工作來處理大部分的內(nèi)存管理,但我們還是建議你了解一下底層發(fā)生的事情。
編譯代碼時,編譯器可以檢測到原始數(shù)據(jù)類型然后提前計算出需要多少內(nèi)存。隨后給??臻g中的程序分配所需額度。分配變量的空間被稱為棧空間是因為當(dāng)函數(shù)調(diào)用時,它們被添加到已有內(nèi)存的頂部。當(dāng)它們終止時,根據(jù)后進先出的原則被移除。例如,考慮如下聲明:
int n; // 4 bytes 4字節(jié) int x[4]; // array of 4 elements, each 4 bytes 含有四個元素的數(shù)組,每個4字節(jié) double m; // 8 bytes 8字節(jié)
編譯器能夠立即看出這段代碼需要4+4*4+8=28字節(jié)。
這是現(xiàn)今處理整型和雙精度浮點數(shù)的大小。20年以前,整型通常是2字節(jié),雙精度是4字節(jié)。代碼永遠不應(yīng)該依賴當(dāng)前基本數(shù)據(jù)類型的大小。
編譯器將會插入代碼與操作系統(tǒng)交互,請求棧上存儲變量所需的字節(jié)數(shù)。
在上面的例子中,編譯器知道每個變量的精確內(nèi)存地址。實際上,每當(dāng)寫入變量 n,它都會在內(nèi)部被轉(zhuǎn)換成類似“內(nèi)存地址4127963”的東西。
注意,如果試圖在這里訪問 x[4],將會訪問到與 m 關(guān)聯(lián)的數(shù)據(jù)。這是因為我們在訪問數(shù)組中一個不存在的元素——比數(shù)組中最后實際分配的成員 x[3] 要遠4個字節(jié),這可能最終會讀取(或?qū)懭耄┮恍?m 中的比特。這必將會使程序其余部分產(chǎn)生非常不希望得到的結(jié)果。
當(dāng)函數(shù)調(diào)用其他函數(shù)時,每個函數(shù)都會在被調(diào)用時得到屬于自己的一塊棧。這里不僅保存了所有的局部變量,還保存著記錄執(zhí)行位置的程序計數(shù)器。當(dāng)函數(shù)結(jié)束時,它的內(nèi)存單元再次變得空閑可供他用。
動態(tài)分配不幸的是,當(dāng)我們在編譯時無法得知變量需要多少內(nèi)存的時候事情就沒那么簡單了。假設(shè)我們要做如下的事情:
int n = readInput(); // reads input from the user ... // create an array with "n" elements
這在編譯時,編譯器無法知道數(shù)組需要多少內(nèi)存,因為它取決于用戶提供的值。
因此無法為棧中的變量分配空間。相反,我們的程序需要在運行時顯式向操作系統(tǒng)請求合適的空間。這種內(nèi)存由堆空間分配。靜態(tài)和動態(tài)內(nèi)存分配的區(qū)別總結(jié)為下表:
要充分理解動態(tài)內(nèi)存分配的原理,我們需要在指針上多花些時間,但這已經(jīng)偏離了本文的主題。如果有興趣學(xué)習(xí)更多,請在評論里留言告訴我們,我們可以在以后的文章中討論更多關(guān)于指針的細節(jié)。
JavaScript 中的分配現(xiàn)在我們將解釋第一步(分配內(nèi)存)如何在 JavaScript 中工作。
JavaScript 將開發(fā)者從內(nèi)存分配的責(zé)任中解放出來——在聲明變量的同時它會自己處理內(nèi)存分配。
var n = 374; // allocates memory for a number 為數(shù)值分配內(nèi)存 var s = "sessionstack"; // allocates memory for a string 為字符串分配內(nèi)存 var o = { a: 1, b: null }; // allocates memory for an object and its contained values 為對象及其包含的值分配內(nèi)存 var a = [1, null, "str"]; // (like object) allocates memory for the // array and its contained values (與對象一樣)為數(shù)組及其包含的值分配內(nèi)存 function f(a) { return a + 3; } // allocates a function (which is a callable object) 分配函數(shù)(即可調(diào)用對象) // function expressions also allocate an object 函數(shù)表達式同樣分配一個對象 someElement.addEventListener("click", function() { someElement.style.backgroundColor = "blue"; }, false);
某些函數(shù)調(diào)用也產(chǎn)生對象分配:
var d = new Date(); // allocates a Date object 分配一個日期對象 var e = document.createElement("div"); // allocates a DOM element 分配一個DOM元素
方法可以分配新的值或?qū)ο螅?/p>
var s1 = "sessionstack"; var s2 = s1.substr(0, 3); // s2 is a new string s2是一個新字符串 // Since strings are immutable, 由于字符串是不可變的 // JavaScript may decide to not allocate memory, JavaScript可能會決定不分配內(nèi)存 // but just store the [0, 3] range. 而僅僅存儲[0, 3]這個范圍 var a1 = ["str1", "str2"]; var a2 = ["str3", "str4"]; var a3 = a1.concat(a2); // new array with 4 elements being 含有四個元素的數(shù)組 // the concatenation of a1 and a2 elements 由a1和a2的元素的結(jié)合在 JavaScript 中使用內(nèi)存
在 JavaScript 中使用分配的內(nèi)存基本上意味著在其中進行讀寫操作。
這可以通過讀取或?qū)懭胱兞康闹祷驅(qū)ο髮傩?、甚至向函?shù)傳參數(shù)的時候?qū)崿F(xiàn)。
在不需要內(nèi)存時將其釋放大多數(shù)內(nèi)存管理問題出現(xiàn)在這個階段。
最大的難題是弄清楚何時不再需要分配的內(nèi)存。通常需要開發(fā)者來決定這塊內(nèi)存在程序的何處不再需要并且釋放它。
高級編程語言嵌入了一個叫做垃圾收集器軟件,它的工作是追蹤內(nèi)存分配和使用以便發(fā)現(xiàn)分配的內(nèi)存何時不再需要,并在這種情況下自動釋放它。
不幸的是這個過程只是個近似的過程,因為知道是否還需要一些內(nèi)存的一般問題是不可決定的(無法靠算法解決)。
大多數(shù)垃圾收集器的工作原理是收集不能再訪問的內(nèi)存,比如指向它的所有變量都超出作用域。但這也是對可收集內(nèi)存空間的一種低估,因為在任何時候作用域內(nèi)都仍可能有一個變量指向一個內(nèi)存地址,然而它再也不會被訪問。
垃圾收集由于無法確定某些內(nèi)存是否“不再需要”,垃圾收集實現(xiàn)了對一般解決方法的限制。這一節(jié)將會解釋理解主要的垃圾收集算法的必要概念和局限性。
內(nèi)存引用垃圾收集算法依賴的主要概念之一是引用。
在內(nèi)存管理的上下文中,如果一個對象可以訪問另一個對象則說成是前者引用了后者(可是隱式也可是顯式)。例如,JavaScript 對象有對其原型的引用(隱式引用)和對屬性的引用(顯式引用)。
在這個上下文中,”對象“的概念擴展到比常規(guī) JavaScript 對象更廣泛的范圍,并且還包含函數(shù)作用域(或全局詞法作用域)。
詞法作用域規(guī)定了如何解析嵌套函數(shù)中的變量名稱:內(nèi)層函數(shù)包含了父函數(shù)的作用域,即使父函數(shù)已返回。引用計數(shù)垃圾收集
這是最簡單的垃圾收集算法。如果沒有指向?qū)ο蟮囊?,就被認為是“可收集的”。
看看如下代碼:
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 // 創(chuàng)建了兩個對象 // o2 被當(dāng)作 o1 的屬性而引用 // 現(xiàn)在沒有可被收集的垃圾 var o3 = o1; // the "o3" variable is the second thing that // has a reference to the object pointed by "o1". // o3是第二個引用了o1 所指向?qū)ο蟮淖兞俊? o1 = 1; // now, the object that was originally in "o1" has a // single reference, embodied by the "o3" variable // 現(xiàn)在,本來被 o1 指向的對象變成了單一引用,體現(xiàn)在 o3 上。 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 // 通過屬性 o2 建立了對它所指對象的引用 // 這個對象現(xiàn)在有兩個引用:一個作為屬性的o2 // 另一個是變量 o4 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. // 原本由 o1 引用的對象現(xiàn)在含有0個引用。 // 它可以被作為垃圾而收集 // 但是它的屬性 o2 仍然被變量 o4 引用,所以它不能被釋放。 o4 = null; // what was the "o2" property of the object originally in // "o1" has zero references to it. // It can be garbage collected. // 原本由 o1 引用的對象的屬性 o2 現(xiàn)在也只有0個引用,它現(xiàn)在可以被收集了。循環(huán)制造出問題
這在循環(huán)引用時存在限制。在下面示例中,創(chuàng)建了兩個互相引用的對象,從而創(chuàng)建了一個循環(huán)。它們在函數(shù)調(diào)用返回后超出作用域,所以實際上它們已經(jīng)沒用了并應(yīng)該被釋放。但引用計數(shù)算法考慮到由于它們至少被引用了一次,所以兩者都不會被當(dāng)作垃圾收集。
function f() { var o1 = {}; var o2 = {}; o1.p = o2; // o1 references o2 o2.p = o1; // o2 references o1. This creates a cycle. } f();標(biāo)記和清理算法
為了決定是否還需要對象,這個算法確定了對象是否可以訪問。
標(biāo)記和清理算法有如下三個步驟:
根:通常,根是被代碼引用的全局變量。例如在 JavaScript 中,可以作為根的全局變量是 window 對象。同一對象在 Node.js 中被稱為 global。垃圾收集器建立了所有根的完整列表。
接著算法檢查所有根及它們的子節(jié)點,并把它們標(biāo)記為活躍的(意為它們不是垃圾)。根所不能獲取到的任何東西都被標(biāo)記為垃圾。
最終,垃圾收集器把未標(biāo)記為活躍的所有內(nèi)存片段釋放并返還給操作系統(tǒng)。
這個算法比之前的更好,因為“一個對象沒有引用”造成這個對象變得不可獲取,但通過循環(huán)我們看到反過來卻是不成立的。
2012年后,所有現(xiàn)代瀏覽器都裝載了標(biāo)記和清理垃圾收集器。近年來,在 JavaScript 垃圾收集所有領(lǐng)域的改善(分代/增量/并發(fā)/并行垃圾收集)都是這個算法(標(biāo)記和清理)的實現(xiàn)改進,既不是垃圾收集算法自身的改進也并非決定是否對象可獲取的目標(biāo)的改進。
在這篇文章中,你可以閱讀到有關(guān)追蹤垃圾收集的大量細節(jié),并且涵蓋了標(biāo)記和清理及它的優(yōu)化。
循環(huán)不再是問題在上面的第一個例子中,當(dāng)函數(shù)調(diào)用返回后,兩個對象不再被全局對象的可獲取節(jié)點引用。結(jié)果是,它們會被垃圾收集齊認為是不可獲取的。
即便它們彼此間仍存在引用,它們也不能被根獲取到。
垃圾收集器與直覺相反的行為雖然垃圾收集器很方便,但它們也有自己的一套折中策略。其一是非確定性。換句話說,垃圾收集是不可預(yù)測的。你無法確切知道垃圾收集什么時候執(zhí)行。這意味著在一些情況下程序會要求比實際需要更多的內(nèi)存。另一些情況下,短時暫停會在一些特別敏感的應(yīng)用中很明顯。雖然非確定性意味著無法確定垃圾收集執(zhí)行的時間,但大多數(shù)垃圾收集的實現(xiàn)都共享一個通用模式:在內(nèi)存分配期間進行收集。如果沒有內(nèi)存分配發(fā)生,垃圾收集器就處于閑置??紤]以下場景:
執(zhí)行大量內(nèi)存分配。
它們大多數(shù)(或全部)被標(biāo)記為不可獲取(假設(shè)我們將一個不再需要的指向緩存的引用置為null)。
不再有進一步的內(nèi)存分配發(fā)生。
在這個場景下,大多數(shù)垃圾收集不會再運行收集傳遞。換言之,即時存在無法訪問的引用可以收集,它們也不會被收集器注意到。這些不是嚴(yán)格意義上的泄露,但是仍然導(dǎo)致了比正常更高的內(nèi)存使用。
什么是內(nèi)存泄露?就像內(nèi)存所暗示的,內(nèi)存泄露是被應(yīng)用使用過的一塊內(nèi)存在不需要時尚未返還給操作操作系統(tǒng)或由于糟糕的內(nèi)存釋放未能返還。
編程語言喜歡用不同的方式進行內(nèi)存管理。但一塊已知內(nèi)存是否還被使用實際上是個無法決定的問題。換句話說,只有開發(fā)人員可以弄清除是否應(yīng)該將一塊內(nèi)存還給操作系統(tǒng)。
某些編程語言提供了開發(fā)人員手動釋放內(nèi)存的特性。另一些則希望由開發(fā)人員完全提供顯式的聲明。維基百科上有關(guān)于手動和自動內(nèi)存管理的好的文章。
四種常見 JavaScript 泄露 1:全局變量JavaScript 處理未聲明變量的方式很有趣:當(dāng)引用一個還未聲明的變量時,就在全局對象上創(chuàng)建一個新變量。在瀏覽器中,全局對象是 window,這意味著:
function foo(arg) { bar = "some text"; }
等價于
function foo(arg) { window.bar = "some text"; }
讓我們假設(shè) bar 僅是為了在函數(shù) foo 中引用變量。但如果不使用 var 聲明,將創(chuàng)建一個多余的全局變量。在上面的例子中,并不會引起多大損害。但你仍可想到一個更具破壞性的場景。
你可以偶然地通過 this 創(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();
可以通過在 JavaScript 文件的開頭添加 "use strict"; 來避免這一切,這會開啟一個更加嚴(yán)格的模式來解析代碼,它可以防止意外創(chuàng)建全局變量。
意外的全局變量當(dāng)然是個問題,但是通常情況下,你的代碼會被顯示全局變量污染,并且根據(jù)定義它們無法被垃圾收集器收集。應(yīng)該尤其注意用來臨時性存儲和處理大量信息的全局變量。如果你必須使用全局變量存儲信息而當(dāng)你這樣做了時,確保一旦完成之后就將它賦值為 null 或重新分配。
2:被遺忘的計時器或回調(diào)讓我們來看看 setInterval 的列子,它在 JavaScript 中經(jīng)常用到。
提供觀察者模式的庫和其他接受回調(diào)函數(shù)的實現(xiàn)通常會在它們的實例無法獲取確保對這些回調(diào)函數(shù)的引用也變成無法獲取。同樣,下面的代碼不難找到:
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.
上面這段代碼展示了引用不再需要的節(jié)點或數(shù)據(jù)的后果。
renderer 對象可能在某個時候被覆蓋或移除,這將會導(dǎo)致封裝在間隔處理函數(shù)中的語句變得冗余。一旦發(fā)生這種情況,處理器和它依賴的東西必須要等到間隔器先被停止之后才能收集(記住,它依然是活躍的)。這將會導(dǎo)致這樣的事實:用于儲存和處理數(shù)據(jù)的 serverData 也將不會被收集。
當(dāng)使用觀察者模式時,你需要在完成后確保通過顯示調(diào)用移除它們(既不再需要觀察者,對象也變成不可獲取的)。
幸運的是,大多數(shù)現(xiàn)代瀏覽器會為我們處理好這些事務(wù):它們會自動收集被觀察對象變成不可獲取的觀察者處理器,即使你忘記移除這些監(jiān)聽器。過去一些瀏覽器是無法做到這些的(老IE6)。
不過,符合最佳實踐的還是在對象過時時移除觀察者。來看下面的例子:
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)在,當(dāng)元素超出作用域之后, // 即使是不能很好處理循環(huán)的老瀏覽器也能將元素和點擊處理函數(shù)回收。
在使節(jié)點變成不可獲取之前不再需要調(diào)用 removeEventListener ,因為現(xiàn)代瀏覽器支持垃圾收集器可以探測這些循環(huán)并進行適當(dāng)處理。
如果你利用 jQuery APIs(其他庫和框架也支持),它也可以在節(jié)點無效之前移除監(jiān)聽器。這個庫也會確保沒有內(nèi)存泄露發(fā)生,即使應(yīng)用運行在老瀏覽器之下。
3:閉包JavaScript 開發(fā)的核心領(lǐng)域之一是閉包:內(nèi)層函數(shù)可以訪問外層(封閉)函數(shù)的變量。 歸咎于 JavaScript 運行時的實現(xiàn)細節(jié),可能發(fā)生下面這樣的內(nèi)存泄露:
var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) // a reference to "originalThing" console.log("hi"); }; theThing = { longStr: new Array(1000000).join("*"), someMethod: function () { console.log("message"); } }; }; setInterval(replaceThing, 1000);
當(dāng) replaceThing 調(diào)用后,theThing 被賦值為一個對象,由一個大數(shù)組和一個新的閉包(someMethod)組成。還有,originalThing 被變量 unused 擁有的閉包所引用(值是上一次 replaceThing 調(diào)用所得到的變量 theThing )。要記住的是當(dāng)一個閉包作用域被創(chuàng)建時,位于同一個父作用域內(nèi)的其他閉包也共享這個作用域。
在這個案列中,為閉包 someMethod 創(chuàng)建的作用域被 unused 共享。即便 unused 從未使用,someMethod 可以通過位于 replaceThing 外層的 theThing 使用(例如,在全局中)。又因為 someMethod 與 unused 共享閉包作用域,unused 引用的 originalThing 被強制處于活躍狀態(tài)(在兩個閉包之間被共享的整個作用域)。這些妨礙了被收集。
在上述列子中,當(dāng) unused 引用了 originalThing 時,共享了為 someMethod 創(chuàng)建的作用域??梢酝ㄟ^ replaceThing 作用域外的 theThing 使用 someMethod,且不管其實 unused 從未使用。事實上 unused 引用了 originalThing 使其保持在活躍狀態(tài),因為someMethod 與 unused 共享了閉包作用域。
所有的這些導(dǎo)致了相當(dāng)大的內(nèi)存泄露。你會看到在上述代碼一遍又一遍運行時內(nèi)存使用量的激增。它不會在垃圾收集器運行時變小。一系列的閉包被創(chuàng)建(此例中根是變量 theThing),每一個閉包作用域都間接引用了大數(shù)組。
Meteor 團隊發(fā)現(xiàn)了這個問題,他們有一篇非常棒的文章詳細描述了這個問題。
4:外部DOM引用還有種情況是當(dāng)開發(fā)人員把 DOM 節(jié)點儲存在數(shù)據(jù)結(jié)構(gòu)里的時候。假設(shè)你想快速更新表格中某幾行的內(nèi)容。如果把對每行的 DOM 引用存在字典中或數(shù)組中,就會存在對相同 DOM 元素的兩份引用:一份在 DOM 樹中一份在字典里。如果想移除這些行,你得記著要把這兩份引用都變成不可獲取的。
var elements = { button: document.getElementById("button"), image: document.getElementById("image") }; function doStuff() { elements.image.src = "http://example.com/image_name.png"; } function removeImage() { // The image is a direct child of the body element. // 圖片是body的直接子元素 document.body.removeChild(document.getElementById("image")); // At this point, we still have a reference to #button in the //global elements object. In other words, the button element is //still in memory and cannot be collected by the GC. // 這時,全局elements對象仍有一個對#button元素的引用。換句話說,button元素 // 仍然在內(nèi)存里,無法被垃圾收集器回收。 }
還有一個例外情況應(yīng)該被考慮到,它出現(xiàn)在引用 DOM 樹的內(nèi)部或葉節(jié)點時。如果你在代碼里保存了一個對表格單元(td 標(biāo)簽)的引用,然后決定把表格從 DOM 中移除但保留對那個特別單元格的引用,就能預(yù)料到將會有大量的內(nèi)存泄露。你可能認為垃圾收集器將釋放其他所有的東西除了那個單元格。但是,這將不會發(fā)生。因為這個單元格是表格的一個子節(jié)點,子節(jié)點保存了對它們父節(jié)點的引用,引用這一個單元格將會在內(nèi)存里保存整個表格。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/106376.html
摘要:是如何工作的內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏原文譯者幾個禮拜之前我們開始一系列對于以及其本質(zhì)工作原理的深入挖掘我們認為通過了解的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應(yīng)用。 JavaScript是如何工作的:內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏 原文:How JavaScript works: memory management + how to han...
摘要:本文作為第三篇,將會討論另一個開發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會提供一些關(guān)于如何處理內(nèi)存泄露的技巧。這是當(dāng)前整型和雙精度的大小。然而,這是一組可以收集的內(nèi)存空間的近似值。 本文轉(zhuǎn)載自:眾成翻譯譯者:Leslie Wang審校: 為之漫筆鏈接:http://www.zcfy.cc/article/4211原文:https://blog.sessionstack.com/how-j...
摘要:這是因為我們訪問了數(shù)組中不存在的數(shù)組元素它超過了最后一個實際分配到內(nèi)存的數(shù)組元素字節(jié),并且有可能會讀取或者覆寫的位。包含個元素的新數(shù)組由和數(shù)組元素所組成中的內(nèi)存使用中使用分配的內(nèi)存主要指的是內(nèi)存讀寫。 原文請查閱這里,本文有進行刪減,文后增了些經(jīng)驗總結(jié)。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會討論日常使用中另一個被開發(fā)...
閱讀 703·2021-11-18 10:02
閱讀 3579·2021-09-02 10:21
閱讀 1725·2021-08-27 16:16
閱讀 2057·2019-08-30 15:56
閱讀 2385·2019-08-29 16:53
閱讀 1372·2019-08-29 11:18
閱讀 2952·2019-08-26 10:33
閱讀 2640·2019-08-23 18:34