成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

adam1q84 / 2983人閱讀

摘要:本系列的第一篇文章著重提供一個(gè)關(guān)于引擎運(yùn)行時(shí)和調(diào)用棧的概述。在硬件層面,計(jì)算機(jī)內(nèi)存由大量的觸發(fā)器組成。每個(gè)觸發(fā)器包含幾個(gè)晶體管能夠存儲(chǔ)一個(gè)比特譯注位??梢酝ㄟ^唯一標(biāo)識(shí)符來訪問單個(gè)觸發(fā)器,所以可以對(duì)它們進(jìn)行讀寫操作。比特稱為個(gè)字節(jié)。

原文 How JavaScript works: memory management + how to handle 4 common memory leaks

幾周前我們開始了一個(gè)系列博文旨在深入挖掘 JavaScript 并弄清楚它的工作原理:我們認(rèn)為通過了解 JavaScript 的構(gòu)建單元并熟悉它們是怎樣結(jié)合起來的,有助于寫出更好的代碼和應(yīng)用。

本系列的第一篇文章著重提供一個(gè)關(guān)于引擎、運(yùn)行時(shí)和調(diào)用棧的概述。第二篇文章深入分析了 GoogleV8 引擎的內(nèi)部實(shí)現(xiàn)并提供了一些編寫更優(yōu)質(zhì) JavaScript 代碼的建議。

在第三篇的本文中,我們將會(huì)討論另一個(gè)非常重要的主題,由于日常使用的編程語言的逐漸成熟和復(fù)雜性,它被越來越多的開發(fā)者忽視——內(nèi)存管理。我們還會(huì)提供一些在 SessionStack 中遵循的關(guān)于如何處理 JavaScript 內(nèi)存泄露的方法,我們必須保證 SessionStack 不會(huì)發(fā)生內(nèi)存泄漏,或?qū)е抡线M(jìn)來的應(yīng)用增加內(nèi)存消耗。

概述

C 這樣的語言,具有低水平的內(nèi)存管理原語如 malloc()free(),這些原語被開發(fā)者用來顯式地向操作系統(tǒng)分配和釋放內(nèi)存。

同時(shí),JavaScript 在事物(對(duì)象、字符串等)被創(chuàng)建時(shí)分配內(nèi)存,并在它們不再需要用到時(shí)自動(dòng)釋放內(nèi)存,這個(gè)過程稱為垃圾收集。這個(gè)看似自動(dòng)釋放資源的特性是困惑的來源,造成 JavaScript(和其他高級(jí)語言)開發(fā)者錯(cuò)誤的印象,認(rèn)為他們可以選擇不必關(guān)心內(nèi)存管理。這是個(gè)天大的誤解。

即便在使用高級(jí)編程語言時(shí),開發(fā)者也應(yīng)該了解內(nèi)存管理(至少最基本的)。有時(shí)會(huì)遇到自動(dòng)內(nèi)存管理的問題(如垃圾收集器的BUG和實(shí)現(xiàn)限制等),開發(fā)者應(yīng)該了解這些問題才能合理地處理它們(或找到適當(dāng)?shù)慕鉀Q方案,用最小的代價(jià)和代碼債)。

內(nèi)存生命周期

無論使用哪種編程語言,內(nèi)存的生命周期幾乎總是相同的:

下面是周期中每個(gè)步驟發(fā)生了什么的概覽:

分配內(nèi)存——內(nèi)存由允許程序使用的操作系統(tǒng)分配。在低級(jí)編程語言(如 C)中這是一個(gè)作為開發(fā)人員應(yīng)該處理的顯式操作。而在高級(jí)編程語言中是由語言本身幫你處理的。

使用內(nèi)存——這是程序?qū)嶋H上使用之前所分配內(nèi)存的階段。讀寫操作發(fā)生在使用代碼中分配的變量時(shí)。

釋放內(nèi)存——現(xiàn)在是釋放不需要的整個(gè)內(nèi)存的時(shí)候了,這樣它才能變得空閑以便再次可用。與分配內(nèi)存一樣,在低級(jí)編程語言中這是一個(gè)顯式操作。

想要快速瀏覽調(diào)用棧和內(nèi)存堆的概念,可以閱讀我們關(guān)于這個(gè)主題的第一篇文章。

什么是內(nèi)存?

在直接介紹 JavaScript 中的內(nèi)存之前,我們會(huì)簡(jiǎn)要討論一下內(nèi)存是什么及它是怎樣工作的。

在硬件層面,計(jì)算機(jī)內(nèi)存由大量的觸發(fā)器組成。每個(gè)觸發(fā)器包含幾個(gè)晶體管能夠存儲(chǔ)一個(gè)比特(譯注:1位)。可以通過唯一標(biāo)識(shí)符來訪問單個(gè)觸發(fā)器,所以可以對(duì)它們進(jìn)行讀寫操作。因此從概念上,我們可以把整個(gè)計(jì)算機(jī)內(nèi)存想象成一個(gè)巨大的可讀寫的比特陣列。

作為人類,我們并不擅長(zhǎng)使用字節(jié)進(jìn)行所有的思考和算術(shù),我們把它們組織成更大的組合,一起用來表示數(shù)字。8比特稱為1個(gè)字節(jié)。除字節(jié)之外,還有其他詞(有時(shí)是16比特、有時(shí)是32比特)。

很多東西存儲(chǔ)在內(nèi)存中:

所有程序使用的所有變量和其他數(shù)據(jù)。

程序代碼,包括操作系統(tǒng)的。

編譯器和操作系統(tǒng)一起工作來處理大部分的內(nèi)存管理,但我們還是建議你了解一下底層發(fā)生的事情。

編譯代碼時(shí),編譯器可以檢測(cè)到原始數(shù)據(jù)類型然后提前計(jì)算出需要多少內(nèi)存。隨后給棧空間中的程序分配所需額度。分配變量的空間被稱為??臻g是因?yàn)楫?dāng)函數(shù)調(diào)用時(shí),它們被添加到已有內(nèi)存的頂部。當(dāng)它們終止時(shí),根據(jù)后進(jìn)先出的原則被移除。例如,考慮如下聲明:

int n; // 4 bytes 4字節(jié)
int x[4]; // array of 4 elements, each 4 bytes 含有四個(gè)元素的數(shù)組,每個(gè)4字節(jié)
double m; // 8 bytes 8字節(jié)

編譯器能夠立即看出這段代碼需要4+4*4+8=28字節(jié)。

這是現(xiàn)今處理整型和雙精度浮點(diǎn)數(shù)的大小。20年以前,整型通常是2字節(jié),雙精度是4字節(jié)。代碼永遠(yuǎn)不應(yīng)該依賴當(dāng)前基本數(shù)據(jù)類型的大小。

編譯器將會(huì)插入代碼與操作系統(tǒng)交互,請(qǐng)求棧上存儲(chǔ)變量所需的字節(jié)數(shù)。

在上面的例子中,編譯器知道每個(gè)變量的精確內(nèi)存地址。實(shí)際上,每當(dāng)寫入變量 n,它都會(huì)在內(nèi)部被轉(zhuǎn)換成類似“內(nèi)存地址4127963”的東西。

注意,如果試圖在這里訪問 x[4],將會(huì)訪問到與 m 關(guān)聯(lián)的數(shù)據(jù)。這是因?yàn)槲覀冊(cè)谠L問數(shù)組中一個(gè)不存在的元素——比數(shù)組中最后實(shí)際分配的成員 x[3] 要遠(yuǎn)4個(gè)字節(jié),這可能最終會(huì)讀取(或?qū)懭耄┮恍?m 中的比特。這必將會(huì)使程序其余部分產(chǎn)生非常不希望得到的結(jié)果。

當(dāng)函數(shù)調(diào)用其他函數(shù)時(shí),每個(gè)函數(shù)都會(huì)在被調(diào)用時(shí)得到屬于自己的一塊棧。這里不僅保存了所有的局部變量,還保存著記錄執(zhí)行位置的程序計(jì)數(shù)器。當(dāng)函數(shù)結(jié)束時(shí),它的內(nèi)存單元再次變得空閑可供他用。

動(dòng)態(tài)分配

不幸的是,當(dāng)我們?cè)诰幾g時(shí)無法得知變量需要多少內(nèi)存的時(shí)候事情就沒那么簡(jiǎn)單了。假設(shè)我們要做如下的事情:

int n = readInput(); // reads input from the user
...
// create an array with "n" elements

這在編譯時(shí),編譯器無法知道數(shù)組需要多少內(nèi)存,因?yàn)樗Q于用戶提供的值。

因此無法為棧中的變量分配空間。相反,我們的程序需要在運(yùn)行時(shí)顯式向操作系統(tǒng)請(qǐng)求合適的空間。這種內(nèi)存由堆空間分配。靜態(tài)和動(dòng)態(tài)內(nèi)存分配的區(qū)別總結(jié)為下表:

要充分理解動(dòng)態(tài)內(nèi)存分配的原理,我們需要在指針上多花些時(shí)間,但這已經(jīng)偏離了本文的主題。如果有興趣學(xué)習(xí)更多,請(qǐng)?jiān)谠u(píng)論里留言告訴我們,我們可以在以后的文章中討論更多關(guān)于指針的細(xì)節(jié)。

JavaScript 中的分配

現(xiàn)在我們將解釋第一步(分配內(nèi)存)如何在 JavaScript 中工作。

JavaScript 將開發(fā)者從內(nèi)存分配的責(zé)任中解放出來——在聲明變量的同時(shí)它會(huì)自己處理內(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  為對(duì)象及其包含的值分配內(nèi)存
var a = [1, null, "str"];  // (like object) allocates memory for the
                           // array and its contained values (與對(duì)象一樣)為數(shù)組及其包含的值分配內(nèi)存
function f(a) {
  return a + 3;
} // allocates a function (which is a callable object) 分配函數(shù)(即可調(diào)用對(duì)象)
// function expressions also allocate an object 函數(shù)表達(dá)式同樣分配一個(gè)對(duì)象
someElement.addEventListener("click", function() {
  someElement.style.backgroundColor = "blue";
}, false);

某些函數(shù)調(diào)用也產(chǎn)生對(duì)象分配:

var d = new Date(); // allocates a Date object 分配一個(gè)日期對(duì)象
var e = document.createElement("div"); // allocates a DOM element 分配一個(gè)DOM元素

方法可以分配新的值或?qū)ο螅?/p>

var s1 = "sessionstack";
var s2 = s1.substr(0, 3); // s2 is a new string s2是一個(gè)新字符串
// Since strings are immutable, 由于字符串是不可變的
// JavaScript may decide to not allocate memory, JavaScript可能會(huì)決定不分配內(nèi)存
// but just store the [0, 3] range. 而僅僅存儲(chǔ)[0, 3]這個(gè)范圍
var a1 = ["str1", "str2"];
var a2 = ["str3", "str4"];
var a3 = a1.concat(a2);
// new array with 4 elements being 含有四個(gè)元素的數(shù)組
// the concatenation of a1 and a2 elements 由a1和a2的元素的結(jié)合
JavaScript 中使用內(nèi)存

JavaScript 中使用分配的內(nèi)存基本上意味著在其中進(jìn)行讀寫操作。

這可以通過讀取或?qū)懭胱兞康闹祷驅(qū)ο髮傩?、甚至向函?shù)傳參數(shù)的時(shí)候?qū)崿F(xiàn)。

在不需要內(nèi)存時(shí)將其釋放

大多數(shù)內(nèi)存管理問題出現(xiàn)在這個(gè)階段。

最大的難題是弄清楚何時(shí)不再需要分配的內(nèi)存。通常需要開發(fā)者來決定這塊內(nèi)存在程序的何處不再需要并且釋放它。

高級(jí)編程語言嵌入了一個(gè)叫做垃圾收集器軟件,它的工作是追蹤內(nèi)存分配和使用以便發(fā)現(xiàn)分配的內(nèi)存何時(shí)不再需要,并在這種情況下自動(dòng)釋放它。

不幸的是這個(gè)過程只是個(gè)近似的過程,因?yàn)橹朗欠襁€需要一些內(nèi)存的一般問題是不可決定的(無法靠算法解決)。

大多數(shù)垃圾收集器的工作原理是收集不能再訪問的內(nèi)存,比如指向它的所有變量都超出作用域。但這也是對(duì)可收集內(nèi)存空間的一種低估,因?yàn)樵谌魏螘r(shí)候作用域內(nèi)都仍可能有一個(gè)變量指向一個(gè)內(nèi)存地址,然而它再也不會(huì)被訪問。

垃圾收集

由于無法確定某些內(nèi)存是否“不再需要”,垃圾收集實(shí)現(xiàn)了對(duì)一般解決方法的限制。這一節(jié)將會(huì)解釋理解主要的垃圾收集算法的必要概念和局限性。

內(nèi)存引用

垃圾收集算法依賴的主要概念之一是引用

在內(nèi)存管理的上下文中,如果一個(gè)對(duì)象可以訪問另一個(gè)對(duì)象則說成是前者引用了后者(可是隱式也可是顯式)。例如,JavaScript 對(duì)象有對(duì)其原型的引用(隱式引用)和對(duì)屬性的引用(顯式引用)。

在這個(gè)上下文中,”對(duì)象“的概念擴(kuò)展到比常規(guī) JavaScript 對(duì)象更廣泛的范圍,并且還包含函數(shù)作用域(或全局詞法作用域)。

詞法作用域規(guī)定了如何解析嵌套函數(shù)中的變量名稱:內(nèi)層函數(shù)包含了父函數(shù)的作用域,即使父函數(shù)已返回。
引用計(jì)數(shù)垃圾收集

這是最簡(jiǎn)單的垃圾收集算法。如果沒有指向?qū)ο蟮囊?,就被認(rèn)為是“可收集的”。

看看如下代碼:

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)建了兩個(gè)對(duì)象
// 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是第二個(gè)引用了o1 所指向?qū)ο蟮淖兞俊?
o1 = 1;      // now, the object that was originally in "o1" has a
            // single reference, embodied by the "o3" variable
            // 現(xiàn)在,本來被 o1 指向的對(duì)象變成了單一引用,體現(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 建立了對(duì)它所指對(duì)象的引用
                // 這個(gè)對(duì)象現(xiàn)在有兩個(gè)引用:一個(gè)作為屬性的o2
                // 另一個(gè)是變量 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 引用的對(duì)象現(xiàn)在含有0個(gè)引用。
            // 它可以被作為垃圾而收集
            // 但是它的屬性 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 引用的對(duì)象的屬性 o2 現(xiàn)在也只有0個(gè)引用,它現(xiàn)在可以被收集了。
循環(huán)制造出問題

這在循環(huán)引用時(shí)存在限制。在下面示例中,創(chuàng)建了兩個(gè)互相引用的對(duì)象,從而創(chuàng)建了一個(gè)循環(huán)。它們?cè)诤瘮?shù)調(diào)用返回后超出作用域,所以實(shí)際上它們已經(jīng)沒用了并應(yīng)該被釋放。但引用計(jì)數(shù)算法考慮到由于它們至少被引用了一次,所以兩者都不會(huì)被當(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)記和清理算法

為了決定是否還需要對(duì)象,這個(gè)算法確定了對(duì)象是否可以訪問。

標(biāo)記和清理算法有如下三個(gè)步驟:

根:通常,根是被代碼引用的全局變量。例如在 JavaScript 中,可以作為根的全局變量是 window 對(duì)象。同一對(duì)象在 Node.js 中被稱為 global。垃圾收集器建立了所有根的完整列表。

接著算法檢查所有根及它們的子節(jié)點(diǎn),并把它們標(biāo)記為活躍的(意為它們不是垃圾)。根所不能獲取到的任何東西都被標(biāo)記為垃圾。

最終,垃圾收集器把未標(biāo)記為活躍的所有內(nèi)存片段釋放并返還給操作系統(tǒng)。

這個(gè)算法比之前的更好,因?yàn)椤耙粋€(gè)對(duì)象沒有引用”造成這個(gè)對(duì)象變得不可獲取,但通過循環(huán)我們看到反過來卻是不成立的。

2012年后,所有現(xiàn)代瀏覽器都裝載了標(biāo)記和清理垃圾收集器。近年來,在 JavaScript 垃圾收集所有領(lǐng)域的改善(分代/增量/并發(fā)/并行垃圾收集)都是這個(gè)算法(標(biāo)記和清理)的實(shí)現(xiàn)改進(jìn),既不是垃圾收集算法自身的改進(jìn)也并非決定是否對(duì)象可獲取的目標(biāo)的改進(jìn)。

在這篇文章中,你可以閱讀到有關(guān)追蹤垃圾收集的大量細(xì)節(jié),并且涵蓋了標(biāo)記和清理及它的優(yōu)化。

循環(huán)不再是問題

在上面的第一個(gè)例子中,當(dāng)函數(shù)調(diào)用返回后,兩個(gè)對(duì)象不再被全局對(duì)象的可獲取節(jié)點(diǎn)引用。結(jié)果是,它們會(huì)被垃圾收集齊認(rèn)為是不可獲取的。

即便它們彼此間仍存在引用,它們也不能被根獲取到。

垃圾收集器與直覺相反的行為

雖然垃圾收集器很方便,但它們也有自己的一套折中策略。其一是非確定性。換句話說,垃圾收集是不可預(yù)測(cè)的。你無法確切知道垃圾收集什么時(shí)候執(zhí)行。這意味著在一些情況下程序會(huì)要求比實(shí)際需要更多的內(nèi)存。另一些情況下,短時(shí)暫停會(huì)在一些特別敏感的應(yīng)用中很明顯。雖然非確定性意味著無法確定垃圾收集執(zhí)行的時(shí)間,但大多數(shù)垃圾收集的實(shí)現(xiàn)都共享一個(gè)通用模式:在內(nèi)存分配期間進(jìn)行收集。如果沒有內(nèi)存分配發(fā)生,垃圾收集器就處于閑置??紤]以下場(chǎng)景:

執(zhí)行大量?jī)?nèi)存分配。

它們大多數(shù)(或全部)被標(biāo)記為不可獲?。僭O(shè)我們將一個(gè)不再需要的指向緩存的引用置為null)。

不再有進(jìn)一步的內(nèi)存分配發(fā)生。

在這個(gè)場(chǎng)景下,大多數(shù)垃圾收集不會(huì)再運(yùn)行收集傳遞。換言之,即時(shí)存在無法訪問的引用可以收集,它們也不會(huì)被收集器注意到。這些不是嚴(yán)格意義上的泄露,但是仍然導(dǎo)致了比正常更高的內(nèi)存使用。

什么是內(nèi)存泄露?

就像內(nèi)存所暗示的,內(nèi)存泄露是被應(yīng)用使用過的一塊內(nèi)存在不需要時(shí)尚未返還給操作操作系統(tǒng)或由于糟糕的內(nèi)存釋放未能返還。

編程語言喜歡用不同的方式進(jìn)行內(nèi)存管理。但一塊已知內(nèi)存是否還被使用實(shí)際上是個(gè)無法決定的問題。換句話說,只有開發(fā)人員可以弄清除是否應(yīng)該將一塊內(nèi)存還給操作系統(tǒng)。

某些編程語言提供了開發(fā)人員手動(dòng)釋放內(nèi)存的特性。另一些則希望由開發(fā)人員完全提供顯式的聲明。維基百科上有關(guān)于手動(dòng)和自動(dòng)內(nèi)存管理的好的文章。

四種常見 JavaScript 泄露 1:全局變量

JavaScript 處理未聲明變量的方式很有趣:當(dāng)引用一個(gè)還未聲明的變量時(shí),就在全局對(duì)象上創(chuàng)建一個(gè)新變量。在瀏覽器中,全局對(duì)象是 window,這意味著:

function foo(arg) {
    bar = "some text";
}

等價(jià)于

function foo(arg) {
    window.bar = "some text";
}

讓我們假設(shè) bar 僅是為了在函數(shù) foo 中引用變量。但如果不使用 var 聲明,將創(chuàng)建一個(gè)多余的全局變量。在上面的例子中,并不會(huì)引起多大損害。但你仍可想到一個(gè)更具破壞性的場(chǎng)景。

你可以偶然地通過 this 創(chuàng)建一個(gè)全局變量:

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"; 來避免這一切,這會(huì)開啟一個(gè)更加嚴(yán)格的模式來解析代碼,它可以防止意外創(chuàng)建全局變量。

意外的全局變量當(dāng)然是個(gè)問題,但是通常情況下,你的代碼會(huì)被顯示全局變量污染,并且根據(jù)定義它們無法被垃圾收集器收集。應(yīng)該尤其注意用來臨時(shí)性存儲(chǔ)和處理大量信息的全局變量。如果你必須使用全局變量存儲(chǔ)信息而當(dāng)你這樣做了時(shí),確保一旦完成之后就將它賦值為 null 或重新分配。

2:被遺忘的計(jì)時(shí)器或回調(diào)

讓我們來看看 setInterval 的列子,它在 JavaScript 中經(jīng)常用到。

提供觀察者模式的庫和其他接受回調(diào)函數(shù)的實(shí)現(xiàn)通常會(huì)在它們的實(shí)例無法獲取確保對(duì)這些回調(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é)點(diǎn)或數(shù)據(jù)的后果。

renderer 對(duì)象可能在某個(gè)時(shí)候被覆蓋或移除,這將會(huì)導(dǎo)致封裝在間隔處理函數(shù)中的語句變得冗余。一旦發(fā)生這種情況,處理器和它依賴的東西必須要等到間隔器先被停止之后才能收集(記住,它依然是活躍的)。這將會(huì)導(dǎo)致這樣的事實(shí):用于儲(chǔ)存和處理數(shù)據(jù)的 serverData 也將不會(huì)被收集。

當(dāng)使用觀察者模式時(shí),你需要在完成后確保通過顯示調(diào)用移除它們(既不再需要觀察者,對(duì)象也變成不可獲取的)。

幸運(yùn)的是,大多數(shù)現(xiàn)代瀏覽器會(huì)為我們處理好這些事務(wù):它們會(huì)自動(dòng)收集被觀察對(duì)象變成不可獲取的觀察者處理器,即使你忘記移除這些監(jiān)聽器。過去一些瀏覽器是無法做到這些的(老IE6)。

不過,符合最佳實(shí)踐的還是在對(duì)象過時(shí)時(shí)移除觀察者。來看下面的例子:

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)的老瀏覽器也能將元素和點(diǎn)擊處理函數(shù)回收。

在使節(jié)點(diǎn)變成不可獲取之前不再需要調(diào)用 removeEventListener ,因?yàn)楝F(xiàn)代瀏覽器支持垃圾收集器可以探測(cè)這些循環(huán)并進(jìn)行適當(dāng)處理。

如果你利用 jQuery APIs(其他庫和框架也支持),它也可以在節(jié)點(diǎn)無效之前移除監(jiān)聽器。這個(gè)庫也會(huì)確保沒有內(nèi)存泄露發(fā)生,即使應(yīng)用運(yùn)行在老瀏覽器之下。

3:閉包

JavaScript 開發(fā)的核心領(lǐng)域之一是閉包:內(nèi)層函數(shù)可以訪問外層(封閉)函數(shù)的變量。 歸咎于 JavaScript 運(yùn)行時(shí)的實(shí)現(xiàn)細(xì)節(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 被賦值為一個(gè)對(duì)象,由一個(gè)大數(shù)組和一個(gè)新的閉包(someMethod)組成。還有,originalThing 被變量 unused 擁有的閉包所引用(值是上一次 replaceThing 調(diào)用所得到的變量 theThing )。要記住的是當(dāng)一個(gè)閉包作用域被創(chuàng)建時(shí),位于同一個(gè)父作用域內(nèi)的其他閉包也共享這個(gè)作用域。

在這個(gè)案列中,為閉包 someMethod 創(chuàng)建的作用域被 unused 共享。即便 unused 從未使用,someMethod 可以通過位于 replaceThing 外層的 theThing 使用(例如,在全局中)。又因?yàn)?someMethodunused 共享閉包作用域,unused 引用的 originalThing 被強(qiáng)制處于活躍狀態(tài)(在兩個(gè)閉包之間被共享的整個(gè)作用域)。這些妨礙了被收集。

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

所有的這些導(dǎo)致了相當(dāng)大的內(nèi)存泄露。你會(huì)看到在上述代碼一遍又一遍運(yùn)行時(shí)內(nèi)存使用量的激增。它不會(huì)在垃圾收集器運(yùn)行時(shí)變小。一系列的閉包被創(chuàng)建(此例中根是變量 theThing),每一個(gè)閉包作用域都間接引用了大數(shù)組。

Meteor 團(tuán)隊(duì)發(fā)現(xiàn)了這個(gè)問題,他們有一篇非常棒的文章詳細(xì)描述了這個(gè)問題。

4:外部DOM引用

還有種情況是當(dāng)開發(fā)人員把 DOM 節(jié)點(diǎn)儲(chǔ)存在數(shù)據(jù)結(jié)構(gòu)里的時(shí)候。假設(shè)你想快速更新表格中某幾行的內(nèi)容。如果把對(duì)每行的 DOM 引用存在字典中或數(shù)組中,就會(huì)存在對(duì)相同 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.
    // 這時(shí),全局elements對(duì)象仍有一個(gè)對(duì)#button元素的引用。換句話說,button元素
    // 仍然在內(nèi)存里,無法被垃圾收集器回收。
}

還有一個(gè)例外情況應(yīng)該被考慮到,它出現(xiàn)在引用 DOM 樹的內(nèi)部或葉節(jié)點(diǎn)時(shí)。如果你在代碼里保存了一個(gè)對(duì)表格單元(td 標(biāo)簽)的引用,然后決定把表格從 DOM 中移除但保留對(duì)那個(gè)特別單元格的引用,就能預(yù)料到將會(huì)有大量的內(nèi)存泄露。你可能認(rèn)為垃圾收集器將釋放其他所有的東西除了那個(gè)單元格。但是,這將不會(huì)發(fā)生。因?yàn)檫@個(gè)單元格是表格的一個(gè)子節(jié)點(diǎn),子節(jié)點(diǎn)保存了對(duì)它們父節(jié)點(diǎn)的引用,引用這一個(gè)單元格將會(huì)在內(nèi)存里保存整個(gè)表格。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106376.html

相關(guān)文章

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

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

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

    摘要:本文作為第三篇,將會(huì)討論另一個(gè)開發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會(huì)提供一些關(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 評(píng)論0 收藏0
  • JavaScript 工作之三-內(nèi)存何處 4常見內(nèi)存泄漏問題(譯)

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

    weknow619 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<