最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript垃圾回收機制實現(xiàn)方法

 更新時間:2025年08月22日 10:59:55   作者:超級無敵大蟑王  
垃圾回收的基本思路是確定哪些變量不會再次被使用,然后回收這些變量占用的內(nèi)存,本文給大家介紹JavaScript垃圾回收機制實現(xiàn)方法,感興趣的朋友跟隨小編一起看看吧

1.垃圾回收的概念

JavaScript使用垃圾自動回收機制進行內(nèi)存管理,無需程序員手動分配和釋放內(nèi)存。垃圾回收的基本思路是確定哪些變量不會再次被使用,然后回收這些變量占用的內(nèi)存。垃圾回收過程是周期性的,垃圾回收程序每隔一段時間會運行一次,垃圾回收也會影響到應(yīng)用程序的性能。常用的垃圾回收機制主要包括標記清除和引用計數(shù)。

1.1 什么是垃圾回收機制:

GCGarbage Collection ,程序工作過程中會產(chǎn)生很多"垃圾",這些垃圾是程序不用的內(nèi)存或者是之前用過了,以后不會再用的內(nèi)存空間,而 GC 就是負責(zé)回收垃圾的,因為他工作在引擎內(nèi)部,所以對于我們前端來說,GC 過程是相對比較無感的,這一套引擎執(zhí)行而對我們又相對無感的操作也就是常說的 垃圾回收機制
不是所有語言都有 GC,一般的高級語言里面會自帶 GC,比如 Java、Python、JavaScript 等,也有無 GC 的語言,比如 C、C++ 等,那這種就需要我們程序員手動管理內(nèi)存了,相對比較麻煩
在像 C/C++ 這樣的語言中,開發(fā)者需要手動分配(malloc)和釋放(free)內(nèi)存。這種方式非常靈活,性能也高,但有兩個致命缺點:

  • 忘記釋放:會導(dǎo)致內(nèi)存泄漏,程序占用的內(nèi)存會隨著時間推移越來越多,最終可能導(dǎo)致程序崩潰或系統(tǒng)變慢。
  • 提前釋放:或釋放了多次,會導(dǎo)致懸掛指針,當(dāng)程序嘗試訪問一個已經(jīng)被釋放的內(nèi)存地址時,會引發(fā)不可預(yù)知的錯誤(通常是程序崩潰)。

JavaScript中存在兩種變量:局部變量全局變量。全局變量的生命周期會持續(xù)到頁面卸載;而局部變量聲明在函數(shù)中,它的生命周期從函數(shù)執(zhí)行開始,直到函數(shù)執(zhí)行結(jié)束,在這個過程中,局部變量會在堆或棧中存儲它們的值,當(dāng)函數(shù)執(zhí)行結(jié)束后,這些局部變量不再被使用,它們所占有的空間就會被釋放。(不過,當(dāng)局部變量被外部函數(shù)使用時,其中一種情況就是閉包,在函數(shù)執(zhí)行結(jié)束后,函數(shù)外部的變量依然指向函數(shù)內(nèi)部的局部變量,此時局部變量依然在被使用,所以不會回收。)

2.垃圾回收機制是如何實現(xiàn)的

2.1核心理念:

GC 機制的核心思想是可達性。簡單來說,就是判斷一個對象是否“可達”,如果不可達,它就是“垃圾”。

  • 可達的對象:以某種方式被訪問或使用的對象。
  • 不可達的對象:無法被訪問到的對象,可以被安全地回收。

GC 會有一系列的對象,它們是可達性的起點。在 JavaScript 中,主要的根包括:

全局對象:比如瀏覽器環(huán)境下的 window 對象,Node.js 環(huán)境下的 global 對象。
因為全局對象在應(yīng)用程序的整個生命周期內(nèi)都存在。只要你的網(wǎng)頁開著,window 對象就永遠不會消失。它是所有全局變量和內(nèi)置API(如 setTimeout, localStorage)的宿主。如果它被回收了,整個JavaScript環(huán)境就都將崩塌。因此,全局對象是GC最重要、最基礎(chǔ)的一個“根”。

函數(shù)調(diào)用棧:當(dāng)前正在執(zhí)行的函數(shù)中的局部變量和參數(shù)。
因為當(dāng)前正在執(zhí)行的代碼和它所依賴的數(shù)據(jù),理所當(dāng)然是“存活”的。調(diào)用棧代表了程序執(zhí)行的“此時此刻”。如果這些正在使用的變量被回收了,程序?qū)⒘⒓闯鲥e。因此,調(diào)用棧中所有棧幀里的變量和參數(shù),都被視為臨時的“根”。

活躍的 DOM 樹:頁面上存在的 DOM 元素。
因為DOM節(jié)點是構(gòu)成用戶界面的實體。用戶能看到、能與之交互的元素,必須始終存在于內(nèi)存中,瀏覽器需要依據(jù)它來進行繪制和響應(yīng)事件。因此,所有在DOM樹上的節(jié)點都被認為是“可達的”。

垃圾回收器會從這些“根”出發(fā),沿著引用鏈進行遍歷。所有能從“根”訪問到的對象,都會被認為是“活”的(可達的);反之,所有無法從“根”訪問到的對象,就會被認為是“死”的(不可達的),并成為垃圾回收的目標。

// 創(chuàng)建一個根對象,并被變量 user 引用
let user = {
  name: "Alice"
};
// 原來的 { name: "Alice" } 對象失去了引用,因此它變成了不可達對象,等待被回收。
user = null;

2.2主流垃圾回收算法

瀏覽器通常使用的垃圾回收方法有兩種:標記清除,引用計數(shù)。

2.2.1標記清除

這是現(xiàn)代瀏覽器中最常用的垃圾回收算法。它完美地解決了循環(huán)引用的問題。

  • 原理:分為兩個階段:
    1. 標記階段:垃圾回收器從“根”對象開始,遍歷所有可達的對象,并在這些對象上打上一個“標記”,表示它們是存活的。
    2. 清除階段:垃圾回收器遍歷整個堆內(nèi)存,所有沒有被標記的對象都被視為垃圾,并被回收,其占用的內(nèi)存被釋放。
  • 優(yōu)點:可以解決循環(huán)引用的問題。因為即使 objAobjB 互相引用,但如果它們都無法從“根”訪問到,那么它們就都不會被標記,最終會被一起清除。
function createCircularReference() {
  let objA = {};
  let objB = {};
  objA.b = objB; 
  objB.a = objA; 
}
createCircularReference();
  • 缺點
    • 執(zhí)行效率問題:GC 執(zhí)行時,需要暫停整個程序的運行,如果堆內(nèi)存很大,標記和清除會很耗時。
    • 內(nèi)存碎片化:清除后,會產(chǎn)生大量不連續(xù)的內(nèi)存碎片。如果之后需要分配一個大對象,可能會因為沒有足夠大的連續(xù)空間而失敗。

2.2.2引用計數(shù)

這是早期的一種 GC 算法,思想非常簡單。

  • 原理:為每個對象維護一個“引用計數(shù)器”。當(dāng)有一個引用指向該對象時,計數(shù)器加1;當(dāng)引用被移除時,計數(shù)器減1。當(dāng)計數(shù)器變?yōu)?時,表示該對象不再被需要,可以被回收。
  • 優(yōu)點:實現(xiàn)簡單,垃圾可以被立即回收,不會有“暫停”的感覺。
  • 致命缺點:無法處理循環(huán)引用。
    看下面的例子:

這種情況下,就要手動釋放變量占用的內(nèi)存:

obj1.a =  null
obj2.a =  null

2.3 V8 引擎的優(yōu)化:分代回收

為了解決標記-清除算法的效率問題,Google 的 V8 引擎(用于 Chrome 和 Node.js)采用了一種更先進的策略:分代回收。
這個策略基于一個重要的觀察:“大部分對象都是朝生夕死的”。也就是說,很多對象在創(chuàng)建后很快就不再被使用,而少數(shù)對象會存活很長時間。

V8 將堆內(nèi)存分為兩個主要區(qū)域:
新生代:Scavenge 算法

  • 特點:存放生命周期短的對象,空間較?。ㄍǔ?1-8MB),垃圾回收頻繁且速度快。
  • 內(nèi)部結(jié)構(gòu):新生代內(nèi)存被平分為兩個相等的空間:From 空間(使用中)To 空間(空閑)。
  • **回收過程:
    1. 新對象首先被分配在 From 空間。
    2. 當(dāng) From 空間快要被占滿時,觸發(fā)一次新生代的 GC。
    3. GC 會檢查 From 空間中的存活對象,并將它們復(fù)制到 To 空間。
    4. 復(fù)制完成后,F(xiàn)rom 空間剩下的所有對象都是垃圾。整個 From 空間被一次性清空。
    5. From 空間和 To 空間的角色互換,等待下一次 GC。
  • 晉升:如果一個對象在新生代中經(jīng)過了多次 Scavenge 依然存活,那么它被認為是生命周期較長的對象,會被“晉升”到老生代中。此外,如果復(fù)制一個對象到 To 空間時,To 空間的使用率超過了25%,該對象也會被直接晉升到老生代。
    老生代:標記-清除與 標記-整理
  • 特點:存放生命周期長或體積大的對象,空間較大,GC 頻率較低。
  • 回收過程
    1. 主要使用標記-清除算法,流程如前所述。
    2. 為了解決內(nèi)存碎片化問題,V8 引入了標記-整理算法。它在標記階段之后,不是直接清除垃圾,而是將所有存活的對象向內(nèi)存的一端移動,然后直接清理掉邊界之外的所有內(nèi)存。這樣就得到了連續(xù)的空閑空間。

3.減少垃圾回收

3.1 手動處理:
雖然瀏覽器可以進行垃圾自動回收,但是當(dāng)代碼比較復(fù)雜時,垃圾回收所帶來的代價比較大,所以應(yīng)該盡量減少垃圾回收。

  • 數(shù)組進行優(yōu)化: 在清空一個數(shù)組時,最簡單的方法就是給其賦值為[ ],但是與此同時會創(chuàng)建一個新的空對象,可以將數(shù)組的長度設(shè)置為0,以此來達到清空數(shù)組的目的。
  • 對象進行優(yōu)化: 對象盡量復(fù)用,對于不再使用的對象,就將其設(shè)置為null,盡快被回收。

避免意外的全局變量
始終使用 constlet 聲明變量,開啟嚴格模式('use strict';)。

function leakyFunction() {
  // 如果沒有 'let' 或 'const', a 會被創(chuàng)建為全局變量
  // 它將永遠不會被回收,除非手動設(shè)為 null
  a = new BigObject();
}

警惕閉包
閉包是 JavaScript 的強大特性,但也很容易造成內(nèi)存泄漏。閉包可以使其父函數(shù)中的變量在函數(shù)執(zhí)行結(jié)束后仍然存活。

function createClosure() {
  let largeData = new Array(1e6).fill('*'); 
  // 這個返回的函數(shù)持有了對 largeData 的引用
  return function() {
  	...
    return largeData.length;
  };
}
let myClosure = createClosure();
// 即使 createClosure 執(zhí)行完畢,largeData 也不會被回收,因為它被 myClosure 引用。
// 如果不再需要它,應(yīng)手動解除引用。
myClosure = null;

定時器和事件監(jiān)聽器
setInterval, setTimeoutaddEventListener 如果不被正確清理,它們的回調(diào)函數(shù)和其引用的外部變量都不會被回收。

let element = document.getElementById('my-button');
let largeData = new BigObject();
function onClick() {
  // do something with largeData
}
element.addEventListener('click', onClick);
// 正確做法:
// element.removeEventListener('click', onClick);
// element = null;

在組件銷毀或元素移除時,務(wù)必使用 clearInterval, clearTimeoutremoveEventListener 清理掉相關(guān)的定時器和監(jiān)聽器。

面試標準答案(背下來?。?br />Q:請簡述 JavaScript 的垃圾回收機制。

A:
JavaScript 的垃圾回收(GC)是自動內(nèi)存管理機制,核心是??標記清除??和??引用計數(shù)??。

  • ??標記清除??:從根對象(如全局變量)出發(fā),標記所有可達對象,清除未標記的內(nèi)存。??優(yōu)點??是解決循環(huán)引用問題,??缺點??是執(zhí)行時可能短暫卡頓。
  • 引用計數(shù)??:記錄每個對象的引用次數(shù),歸零時立即回收。??缺點??是無法處理循環(huán)引用,可能導(dǎo)致內(nèi)存泄漏。

V8 引擎的優(yōu)化:

  • 分代回收:新生代(存活時間短)用Scavenge算法,老生代用標記清除/整理。
  • 增量回收:將GC任務(wù)拆分成小任務(wù),減少卡頓。。

避免內(nèi)存泄漏:

  • 避免意外全局變量。
  • 及時清除定時器、事件監(jiān)聽。
  • 謹慎使用閉包引用大對象。
  • 清理無用的 DOM 引用。

到此這篇關(guān)于JavaScript垃圾回收機制的文章就介紹到這了,更多相關(guān)js垃圾回收機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

浙江省| 南和县| 太保市| 彝良县| 独山县| 平武县| 佛山市| 远安县| 汶川县| 仁寿县| 五河县| 蓝田县| 张家口市| 石屏县| 天长市| 双柏县| 浮山县| 那坡县| 富民县| 辽宁省| 南溪县| 黄龙县| 泽普县| 蓝田县| 罗定市| 凤城市| 怀安县| 宜兰县| 麦盖提县| 武穴市| 上犹县| 孟津县| 南平市| 修文县| 株洲市| 龙泉市| 博客| 扶绥县| 连城县| 平湖市| 大兴区|