国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[譯文] JavaScript工作原理:內(nèi)存管理+如何處理4種常見的內(nèi)存泄露

adam1q84 / 2951人閱讀

摘要:本系列的第一篇文章著重提供一個關(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)用棧的概述。第二篇文章深入分析了 GoogleV8 引擎的內(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 使用(例如,在全局中)。又因為 someMethodunused 共享閉包作用域,unused 引用的 originalThing 被強制處于活躍狀態(tài)(在兩個閉包之間被共享的整個作用域)。這些妨礙了被收集。

在上述列子中,當(dāng) unused 引用了 originalThing 時,共享了為 someMethod 創(chuàng)建的作用域??梢酝ㄟ^ replaceThing 作用域外的 theThing 使用 someMethod,且不管其實 unused 從未使用。事實上 unused 引用了 originalThing 使其保持在活躍狀態(tài),因為someMethodunused 共享了閉包作用域。

所有的這些導(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

相關(guān)文章

  • [譯]JavaScript工作內(nèi)存以及何處常見內(nèi)存泄漏

    摘要:是如何工作的內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏原文譯者幾個禮拜之前我們開始一系列對于以及其本質(zhì)工作原理的深入挖掘我們認為通過了解的構(gòu)建方式以及它們是如何共同合作的,你就能夠?qū)懗龈玫拇a以及應(yīng)用。 JavaScript是如何工作的:內(nèi)存管理以及如何處理四種常見的內(nèi)存泄漏 原文:How JavaScript works: memory management + how to han...

    tianren124 評論0 收藏0
  • 【譯】JavaScript工作內(nèi)存 + 何處4常見內(nèi)存泄露

    摘要:本文作為第三篇,將會討論另一個開發(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...

    IntMain 評論0 收藏0
  • JavaScript 工作之三-內(nèi)存何處 4常見內(nèi)存泄漏問題(譯)

    摘要:這是因為我們訪問了數(shù)組中不存在的數(shù)組元素它超過了最后一個實際分配到內(nèi)存的數(shù)組元素字節(jié),并且有可能會讀取或者覆寫的位。包含個元素的新數(shù)組由和數(shù)組元素所組成中的內(nèi)存使用中使用分配的內(nèi)存主要指的是內(nèi)存讀寫。 原文請查閱這里,本文有進行刪減,文后增了些經(jīng)驗總結(jié)。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會討論日常使用中另一個被開發(fā)...

    weknow619 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<