亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript面試必備之垃圾回收機(jī)制和內(nèi)存泄漏詳解

 更新時(shí)間:2023年05月11日 10:05:40   作者:白哥學(xué)前端  
垃圾回收機(jī)制和內(nèi)存泄漏是JavaScript面試時(shí)常常問(wèn)到的問(wèn)題,這篇文章就為大家詳細(xì)整理了他們的相關(guān)知識(shí),感興趣的小伙伴可以跟隨小編一起了解一下

1.垃圾回收機(jī)制

《JavaScript權(quán)威指南(第四版)》:由于字符串、對(duì)象和數(shù)組沒(méi)有固定大小,所有當(dāng)他們的大小已知時(shí),才能對(duì)他們進(jìn)行動(dòng)態(tài)的存儲(chǔ)分配。JavaScript程序每次創(chuàng)建字符串、數(shù)組或?qū)ο髸r(shí),解釋器都必須分配內(nèi)存來(lái)存儲(chǔ)那個(gè)實(shí)體。只要像這樣動(dòng)態(tài)地分配了內(nèi)存,最終都要釋放這些內(nèi)存以便他們能夠被再用,否則,JavaScript的解釋器將會(huì)消耗完系統(tǒng)中所有可用的內(nèi)存,造成系統(tǒng)崩潰。

這段話(huà)解釋了為什么需要系統(tǒng)需要垃圾回收,JavaScript不像C/C++,它有自己的一套垃圾回收機(jī)制。

JavaScript垃圾回收的機(jī)制:找出不再使用的變量,然后釋放掉其占用的內(nèi)存,但是這個(gè)過(guò)程不是時(shí)時(shí)的,因?yàn)槠溟_(kāi)銷(xiāo)比較大,所以垃圾回收器會(huì)按照固定的時(shí)間間隔周期性的執(zhí)行。

var a = "掘金";
var b = "淘金";
var a = b;

這段代碼運(yùn)行之后,“掘金”這個(gè)字符串失去了引用(之前是被a引用),系統(tǒng)檢測(cè)到這個(gè)事實(shí)之后,就會(huì)釋放該字符串的存儲(chǔ)空間以便這些空間可以被再利用。

那是怎么進(jìn)行垃圾回收的呢?

其實(shí)垃圾回收有兩種方法:標(biāo)記清除、引用計(jì)數(shù)。引用計(jì)數(shù)不太常用,標(biāo)記清除較為常用。

1.1 標(biāo)記清除

這是javascript中最常用的垃圾回收方式。當(dāng)變量進(jìn)入執(zhí)行環(huán)境是,就標(biāo)記這個(gè)變量為“進(jìn)入環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到他們。當(dāng)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為“離開(kāi)環(huán)境”。

垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪(fǎng)問(wèn)到這些變量了。最后。垃圾收集器完成內(nèi)存清除工作,銷(xiāo)毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間。

我們用個(gè)例子,解釋下這個(gè)方法:

var m = 0,n = 19 // 把 m,n,add() 標(biāo)記為進(jìn)入環(huán)境。
add(m, n) // 把 a, b, c標(biāo)記為進(jìn)入環(huán)境。
console.log(n) // a,b,c標(biāo)記為離開(kāi)環(huán)境,等待垃圾回收。
function add(a, b) {
  a++
  var c = a + b
  return c
} 

1.2 引用計(jì)數(shù)

所謂"引用計(jì)數(shù)"是指語(yǔ)言引擎有一張"引用表",保存了內(nèi)存里面所有的資源(通常是各種值)的引用次數(shù)。如果一個(gè)值的引用次數(shù)是0,就表示這個(gè)值不再用到了,因此可以將這塊內(nèi)存釋放

如果一個(gè)值不再需要了,引用數(shù)卻不為0,垃圾回收機(jī)制無(wú)法釋放這塊內(nèi)存,從而導(dǎo)致內(nèi)存泄漏。

var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
console.log('浪里行舟'); 

上面代碼中,數(shù)組[1, 2, 3, 4]是一個(gè)值,會(huì)占用內(nèi)存。變量arr是僅有的對(duì)這個(gè)值的引用,因此引用次數(shù)為1。盡管后面的代碼沒(méi)有用到arr,它還是會(huì)持續(xù)占用內(nèi)存。至于如何釋放內(nèi)存,我們下文介紹。

第三行代碼中,數(shù)組[1, 2, 3, 4]引用的變量arr又取得了另外一個(gè)值,則數(shù)組[1, 2, 3, 4]的引用次數(shù)就減1,此時(shí)它引用次數(shù)變成0,則說(shuō)明沒(méi)有辦法再訪(fǎng)問(wèn)這個(gè)值了,因而就可以將其所占的內(nèi)存空間給收回來(lái)。

但是引用計(jì)數(shù)有個(gè)最大的問(wèn)題: 循環(huán)引用

function func() {
    let obj1 = {};
    let obj2 = {};
    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
} 

當(dāng)函數(shù) func 執(zhí)行結(jié)束后,返回值為 undefined,所以整個(gè)函數(shù)以及內(nèi)部的變量都應(yīng)該被回收,但根據(jù)引用計(jì)數(shù)方法,obj1 和 obj2 的引用次數(shù)都不為 0,所以他們不會(huì)被回收。

要解決循環(huán)引用的問(wèn)題,最好是在不使用它們的時(shí)候手工將它們?cè)O(shè)為空。上面的例子可以這么做:

obj1 = null;
obj2 = null; 

2.什么是內(nèi)存泄漏

2.1 JavaScript內(nèi)存分配和回收的關(guān)鍵詞:GC根、作用域

GC根:一般指全局且不會(huì)被垃圾回收的對(duì)象,比如:window、document或者是頁(yè)面上存在的dom元素。JavaScript的垃圾回收算法會(huì)判斷某塊對(duì)象內(nèi)存是否是GC根可達(dá)(存在一條由GC根對(duì)象到該對(duì)象的引用),如果不是那這塊內(nèi)存將會(huì)被標(biāo)記回收。

作用域:在JavaScript的作用域里,我們能夠新建對(duì)象來(lái)分配內(nèi)存。比如說(shuō)調(diào)用函數(shù),函數(shù)執(zhí)行的過(guò)程中就會(huì)創(chuàng)建一塊作用域,如果是創(chuàng)建的是作用域內(nèi)的局部對(duì)象,當(dāng)作用域運(yùn)行結(jié)束后,所有的局部對(duì)象(GC根無(wú)法觸及)都會(huì)被標(biāo)記回收,在JavaScript中能引起作用域分配的有函數(shù)調(diào)用、with和全局作用域。

我們知道瀏覽器會(huì)把object保存在堆內(nèi)存中,它們通過(guò)索引鏈可以被訪(fǎng)問(wèn)到。GC(Garbage Collector) 是一個(gè)JavaScript引擎的后臺(tái)進(jìn)程,它可以鑒別哪些對(duì)象是已經(jīng)處于無(wú)用的狀態(tài),移除它們,釋放占用的內(nèi)存。

本該被GC回收的變量,如果被其他對(duì)象索引,而且可以通過(guò)root訪(fǎng)問(wèn)到,這就意味著內(nèi)存中存在了冗余的內(nèi)存占用,會(huì)導(dǎo)致應(yīng)用的性能降級(jí),這時(shí)也就發(fā)生了內(nèi)存泄漏。

總結(jié):所謂的內(nèi)存泄漏簡(jiǎn)單來(lái)說(shuō)就是不再用到的內(nèi)存,沒(méi)有得到及時(shí)的釋放。

3.常見(jiàn)的幾種內(nèi)存泄漏的方式

3.1 未被注意的全局變量

全局變量可以被root訪(fǎng)問(wèn),不會(huì)被GC回收。一些非嚴(yán)格模式下的局部變量可能會(huì)變成全局變量,導(dǎo)致內(nèi)存泄漏。

  • 給沒(méi)有聲明的變量賦值
  • this 指向全局對(duì)象
function createGlobalVariables() {
  leaking1 = 'I leak into the global scope'; // assigning value to the undeclared variable
  this.leaking2 = 'I also leak into the global scope'; // 'this' points to the global object
};
createGlobalVariables();
window.leaking1; // 'I leak into the global scope'
window.leaking2; // 'I also leak into the global scope'

如何避免?使用嚴(yán)格模式。

3.2 閉包

閉包函數(shù)執(zhí)行完成后,作用域中的變量不會(huì)被回收,可能會(huì)導(dǎo)致內(nèi)存泄漏:

function outer() {
  const potentiallyHugeArray = [];
  return function inner() {
    potentiallyHugeArray.push('Hello'); // function inner is closed over the potentiallyHugeArray variable
    console.log('Hello');
  };
};
const sayHello = outer(); // contains definition of the function inner
function repeat(fn, num) {
  for (let i = 0; i < num; i++){
    fn();
  }
}
repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray 
// now imagine repeat(sayHello, 100000)

3.3 定時(shí)器

使用setTimeout 或者 setInterval:

function setCallback() {
  const data = {
    counter: 0,
    hugeString: new Array(100000).join('x')
  };
  return function cb() {
    data.counter++; // data object is now part of the callback's scope
    console.log(data.counter);
  }
}
setInterval(setCallback(), 1000); // how do we stop it?

只有當(dāng)定時(shí)器被清理掉的時(shí)候,它回調(diào)函數(shù)內(nèi)部的data才會(huì)被從內(nèi)存中清理,否則在應(yīng)用退出前一直會(huì)被保留。

如何避免?

function setCallback() {
  // 'unpacking' the data object
  let counter = 0;
  const hugeString = new Array(100000).join('x'); // gets removed when the setCallback returns
  return function cb() {
    counter++; // only counter is part of the callback's scope
    console.log(counter);
  }
}
const timerId = setInterval(setCallback(), 1000); // saving the interval ID
// doing something ...
clearInterval(timerId); // stopping the timer i.e. if button pressed

定時(shí)器賦值給timerId,使用clearInterval(timerId)手動(dòng)清理。

3.4Event listeners

addEventListener 也會(huì)一直保留在內(nèi)存中無(wú)法回收,直到我們使用了 removeEventListener,或者添加監(jiān)聽(tīng)事件的DOM被移除。

const hugeString = new Array(100000).join('x');
document.addEventListener('keyup', function() { // anonymous inline function - can't remove it
  doSomething(hugeString); // hugeString is now forever kept in the callback's scope
});

如何避免?

function listener() {
  doSomething(hugeString);
}
document.addEventListener('keyup', listener); // named function can be referenced here...
document.removeEventListener('keyup', listener); // ...and here
// 或者
document.addEventListener('keyup', function listener() {
  doSomething(hugeString);
}, {once: true}); // listener will be removed after running once

4.使用chrome devtools的排查方法

下面使用幾個(gè)案例來(lái)展示在chrome devtools如何查看內(nèi)存泄漏。

4.1 用全局變量緩存數(shù)據(jù)

將全局變量作為緩存數(shù)據(jù)的一種方式,將之后要用到的數(shù)據(jù)都掛載到全局變量上,用完之后也不手動(dòng)釋放內(nèi)存(因?yàn)槿肿兞恳玫膶?duì)象,垃圾回收機(jī)制不會(huì)自動(dòng)回收),全局變量逐漸就積累了一些不用的對(duì)象,導(dǎo)致內(nèi)存泄漏

   var x = [];
    function createSomeNodes() {
        var div;
        var i = 10000;
        var frag = document.createDocumentFragment();
        for (; i > 0; i--) {
            div = document.createElement("div");
            div.appendChild(document.createTextNode(i + " - " + new Date().toTimeString()));
            frag.appendChild(div);
        }
        document.getElementById("nodes").appendChild(frag);
    }
    function grow() {
        x.push(new Array(1000000).join('x'));
        createSomeNodes();
        setTimeout(grow, 1000);
    }
    grow()

上面的代碼貼一張 timeline的截圖

主要看memory區(qū)域,通過(guò)分析代碼我們可以知道頁(yè)面上的dom節(jié)點(diǎn)是不斷增加的,所以memory里綠色的線(xiàn)(代表dom nodes)也是不斷升高的;而代表js heap的藍(lán)色的線(xiàn)是有升有降,當(dāng)整體趨勢(shì)是逐漸升高,這是因?yàn)閖s 有內(nèi)存回收機(jī)制,每當(dāng)內(nèi)存回收的時(shí)候藍(lán)色的線(xiàn)就會(huì)下降,但是存在部分內(nèi)存一直得不到釋放,所以藍(lán)色的線(xiàn)逐漸升高

4.2 js錯(cuò)誤引用DOM元素

    var nodes = '';
    (function () {
        var item = {
            name:new Array(1000000).join('x')
        }
        nodes = document.getElementById("nodes")
        nodes.item = item
        nodes.parentElement.removeChild(nodes)
    })()

這里的dom元素雖然已經(jīng)從頁(yè)面上移除了,但是js中仍然保存這對(duì)該dom元素的引用。

因?yàn)檫@段代碼是只執(zhí)行一次的,所以用timeline視圖會(huì)很難分析出來(lái)是否存在內(nèi)存泄漏,所以我們可以用 chrome dev tool 的 profile tab里的heap snapshot 工具來(lái)分析。

上面的代碼貼一張 heap snapshot 的summary模式的截圖

通過(guò)constructor的filter功能,我們把上面代碼中創(chuàng)建的長(zhǎng)字符串找出來(lái),可以看到代碼運(yùn)行結(jié)束后,內(nèi)存中的長(zhǎng)字符串依然沒(méi)有被垃圾回收掉。
順帶提一下的是右邊紅框里的shadow size和 retainer size的含義

  • shadow size 指的是對(duì)象本地的大小
  • retainer size 指的是對(duì)象所引用內(nèi)存的大小,回收該對(duì)象是會(huì)將他引用的內(nèi)存也一并回收,所以retainer size 指代的是回收內(nèi)存后會(huì)釋放出來(lái)的內(nèi)存大小

上面我們可以看到 長(zhǎng)字符串本身的shadow size和retainer size是一樣大的,這是引用長(zhǎng)字符串沒(méi)有引用其他的對(duì)象,如果有引用其他對(duì)象,那shadow size 和retainer size將不一致。

4.3 閉包循環(huán)引用

(function(){
    var theThing = null
    var replaceThing = function () {
        var originalThing = theThing
        var unused = function () {
            if (originalThing)
                console.log("hi")
        }
        theThing = {
            longStr: new Array(1000000).join('*'),
            someMethod: function someMethod() {
                console.log('someMessage')
            }
        };
    };
    setInterval(replaceThing,100)
})()

首先我們明確一下,unused是一個(gè)閉包,因?yàn)樗昧俗杂勺兞?originalThing,雖然它被沒(méi)有使用,但v8引擎并不會(huì)把它優(yōu)化掉,因?yàn)?JavaScript里存在eval函數(shù),所以v8引擎并不會(huì)隨便優(yōu)化掉暫時(shí)沒(méi)有使用的函數(shù)。

theThing 引用了someMethod,someMethod這個(gè)函數(shù)作用域隱式的和unused這個(gè)閉包共享一個(gè)閉包上下文。所以someMethod也引用了originalThing這個(gè)自由變量。

這里面的引用鏈?zhǔn)牵?/p>

GCHandler -> replaceThing -> theThing -> someMethod -> originalThing -> someMethod(old) -> originalThing(older)-> someMethod(older)

隨著setInterval的不斷執(zhí)行,這條引用鏈?zhǔn)遣粫?huì)斷的,所以?xún)?nèi)存會(huì)不斷泄漏,直致程序崩潰。

因?yàn)槭情]包作用域引起的內(nèi)存泄漏,這時(shí)候最好的選擇是使用 chrome的heap snapshot的container視圖,我們通過(guò)container視圖能清楚的看到這條不斷泄漏內(nèi)存的引用鏈

到此這篇關(guān)于JavaScript面試必備之垃圾回收機(jī)制和內(nèi)存泄漏詳解的文章就介紹到這了,更多相關(guān)JavaScript垃圾回收 內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS判斷不能為空實(shí)例代碼

    JS判斷不能為空實(shí)例代碼

    這篇文章主要介紹了JS判斷不能為空實(shí)例代碼,有需要的朋友可以參考一下
    2013-11-11
  • reveal.js PPT制作框架使用教程

    reveal.js PPT制作框架使用教程

    reveal.js是一款開(kāi)源的HTML演示框架,由Hakim El Hattab開(kāi)發(fā),遵循MIT許可證,它支持嵌套幻燈片、Markdown、自動(dòng)動(dòng)畫(huà)、PDF導(dǎo)出等多種功能,本文就來(lái)介紹一下如何使用,感興趣的可以了解一下
    2024-09-09
  • 詳解如何消除axios攔截中的if

    詳解如何消除axios攔截中的if

    在使用vue的開(kāi)發(fā)中,我們會(huì)使用axios來(lái)做前后端通信,那這時(shí)候我們就需要對(duì)請(qǐng)求前后做攔截,下面這篇文章主要給大家介紹了關(guān)于如何消除axios攔截中if的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • JavaScript登錄記住密碼操作(超簡(jiǎn)單代碼)

    JavaScript登錄記住密碼操作(超簡(jiǎn)單代碼)

    本文給大家分享一段簡(jiǎn)單的js代碼實(shí)現(xiàn)用戶(hù)登錄記住密碼操作,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-03-03
  • ajaxControlToolkit AutoCompleteExtender的用法

    ajaxControlToolkit AutoCompleteExtender的用法

    昨天在搜索中使用了這個(gè)控件,不過(guò)不知道為什么在IE中反應(yīng)比較慢
    2008-10-10
  • javascript和php使用ajax通信傳遞JSON的實(shí)例

    javascript和php使用ajax通信傳遞JSON的實(shí)例

    今天小編就為大家分享一篇javascript和php使用ajax通信傳遞JSON的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • JavaScript插件化開(kāi)發(fā)教程(六)

    JavaScript插件化開(kāi)發(fā)教程(六)

    本文是javascript插件化開(kāi)發(fā)系列教程的第六篇文章,還是重點(diǎn)對(duì)上一篇文章不足的地方進(jìn)行改進(jìn)重構(gòu),逐步分析讓大家能有一個(gè)新的認(rèn)識(shí),希望小伙伴們能夠喜歡。
    2015-02-02
  • JS 中可以提升幸福度的小技巧(可以識(shí)別更多另類(lèi)寫(xiě)法)

    JS 中可以提升幸福度的小技巧(可以識(shí)別更多另類(lèi)寫(xiě)法)

    本文主要介紹一些JS中用到的小技巧,可以在日常Coding中提升幸福度,將不定期更新
    2018-07-07
  • JS實(shí)現(xiàn)數(shù)組扁平化的方法分享

    JS實(shí)現(xiàn)數(shù)組扁平化的方法分享

    數(shù)組扁平化指的是:將一個(gè)多層嵌套的數(shù)組,處理成只有一層的數(shù)組。本文主要和大家介紹了幾個(gè)常用的JS數(shù)組扁平化方法,希望對(duì)大家有所幫助
    2023-04-04
  • 小程序?qū)崿F(xiàn)搜索框功能

    小程序?qū)崿F(xiàn)搜索框功能

    這篇文章主要為大家詳細(xì)介紹了小程序?qū)崿F(xiàn)搜索框功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07

最新評(píng)論