Angular進階之十二:Chrome DevTools+Angular實戰診斷指南

引言

最近有一個工單是說用戶在使用我們的系統的時候,如果使用某個頁面的次數多了以后瀏覽器就開始變慢甚至卡死崩潰掉。這個問題明顯是提示有內存泄露,今天就由這個問題開始分享一些關于內存泄漏的知識。

一、?Web 應用內存泄漏的危害與易忽略性

危害:

  • 性能下降:內存泄漏導致瀏覽器內存占用持續增長,頁面卡頓、響應延遲,最終可能崩潰
  • 資源耗盡:移動設備電池消耗加劇,低端設備體驗惡化
  • 跨頁面影響:SPA(單頁應用)因無頁面刷新,泄漏累積更嚴重

易忽略原因:

  • 漸進性:泄漏初期癥狀不明顯,用戶僅感知輕微卡頓,難及時反饋
  • 工具缺失:開發者缺乏實時監控手段,需主動使用 DevTools 分析
  • 框架依賴:Vue/React 等框架的自動回收機制讓開發者誤以為無需手動管理

二、?什么是內存泄漏

  • 核心概念:程序申請內存后未釋放,導致無效內存占用持續增長
  • JavaScript 中的表現:對象未被 GC 回收。即使不再使用,仍被全局變量、閉包或事件監聽器引用

常見場景:

1. 意外的全局變量

// javascript
function leak() {leakedVar = '全局泄漏'; // 未使用var/let/const,成為window屬性
}
  • 原因:未聲明的變量會掛載到window對象,直到頁面關閉才釋放。
  • 解決:嚴格模式(‘use strict’)或明確聲明變量

2. 未清除的定時器與回調

// javascript
const intervalId = setInterval(() => {// 重復操作
}, 1000);
// 未調用 clearInterval(intervalId)
  • 影響:定時器持續引用函數,阻止垃圾回收(GC)。
  • 解決:在組件卸載或不再需要時清除定時器(clearInterval/clearTimeout)。

3. DOM引用未釋放

// javascript
const elements = {button: document.getElementById('myButton')
};
document.body.removeChild(elements.button); // 移除了DOM節點
// 但 elements.button 仍保留引用
  • 原因:JavaScript對象持有DOM引用,即使節點已從DOM樹移除。
  • 解決:移除節點后手動置空引用(elements.button = null)

4. 閉包引用

// javascript
function createClosure() {const largeData = new Array(1000000).fill('data');return () => console.log(largeData); // 閉包持有largeData
}
const closure = createClosure(); // largeData無法被GC回收
  • 風險:閉包可能無意中持有大型數據結構。
  • 解決:避免在閉包中保留不必要的數據,必要時手動解除引用。

5.未移除的事件監聽器

// javascript
document.addEventListener('click', handleClick);
// 頁面卸載時未調用 removeEventListener

  • 后果:事件監聽器阻止相關對象被回收。
  • 解決:使用removeEventListener或在框架中利用生命周期鉤子(如Angular的OnDestroy清理函數)

6. Web Workers未終止

// javascript
const worker = new Worker('worker.js');
// 未調用 worker.terminate()
  • 影響:Worker線程持續占用內存。
  • 解決:在不需要時調用worker.terminate()。

7. 緩存無限增長

// javascript
const cache = {};
function cacheData(key, data) {cache[key] = data; // 無緩存淘汰機制
}
  • 問題:緩存未設置上限或過期策略。
  • 解決:實現LRU(最近最少使用)等緩存淘汰策略。

三、?出發去找內存泄露

我們可以最大限度利用Chrome提供的工具來診斷內存泄露,我們一般有如下幾種方式來診斷:

  • 堆快照分析: 使用Chrome DevTools的堆快照功能記錄內存狀態,通過比較操作前后的快照差異來定位泄露對象。 對比視圖(Comparison View)可顯示操作后新增或未釋放的對象,幫助確認泄露。 重點關注DOM節點泄露,例如已分離的DOM子樹(Detached DOM Tree)因未被垃圾回收而持續占用內存。
  • 分配分析器工具: 通過分配分析器(Allocation Profiler)實時跟蹤內存分配,識別頻繁創建且未釋放的對象。
  • 保留路徑分析: 在堆快照中檢查對象的保留路徑(Retainers),分析為何對象未被釋放。可忽略無關保留器以簡化分析。
  • 重復字符串與閉包檢查: 過濾重復字符串(Duplicate Strings)和閉包(Closures),命名函數有助于區分閉包內存占用

四、開始診斷

如果你的應用要運行在移動端的瀏覽器中,那么對于內存的使用會更嚴格一些。需要在不同性能的設備上進行測試。但是我們這次主要是面對的是PC端,所以在測試環節會沒有那么復雜。

1. 使用 Chrome 任務管理器實時監控內存使用情況

使用 Chrome 任務管理器作為調查內存問題的起點。Task Manager 是一個實時監視器,類似windows任務管理器,它能告訴頁面使用了多少內存。

  • 按 Shift+Esc 或者從 Chrome 主菜單選擇 更多工具 > 任務管理器 打開任務管理器
  • 然后右鍵單擊 Task Manager 的表窗口啟用 JavaScript 內存 。

  • 實際的效果

  • Memory footprint (內存占用) 列表示 OS 內存。DOM 節點存儲在 OS 內存中。如果此值增加,則表示正在創建 DOM 節點。
  • JavaScript Memory 列表示 JS 堆。這個列包含兩個值。值得注意的是實時數字(括號中的數字)。活動數字表示頁面上的可訪問對象使用的內存量。如果此數量增加,則表示正在創建新對象,或者現有對象正在增長。

2. 使用性能記錄可視化內存泄漏

可以使用Performance(性能)面板作為另一種調查方式。Performance(性能)面板可以讓我們可視化的調查內存隨著時間推移的使用情況.

  • 在 DevTools 中打開 Performance (性能 ) 面板。
  • 啟用 Memory 復選框。

  • 進行錄制 ,最好在每次開始錄制和結束前進行強制垃圾回收,點擊小掃帚圖標進行垃圾回收。
  • 實際效果

記錄下每次的內存數據,然后強制GC再次記錄。觀察如果內存數據持續增加不會被GC釋放,則說明可能存在內存泄漏。

3. 上述的兩種辦法是初步的判斷辦法,下面我們以診斷分離樹造成的內存泄漏為例,進行進一步分析。

首先什么是分離樹(Detached DOM Tree)? 在v8執行GC的時候只有當頁面的 DOM 樹或 JavaScript 代碼中沒有對 DOM 節點的引用時,才能對 DOM 節點進行垃圾回收。當一個節點從 DOM 樹中刪除時,該節點會成為 “detached”的狀態,但如果某些 JavaScript 仍然引用它就會造成內存泄露。

分離的 DOM 節點是內存泄漏的常見原因。這里使用 DevTools 的堆分析器來識別分離的節點。

好的,我們先開始新建一個Angular的簡單APP

在Page1中,添加了監聽事件統計鼠標的點擊次數,隨著點擊次數的增加,改變背景顏色。 代碼如下:

page1.html

  <div><h1>This is page 1</h1><page-click-counter></page-click-counter></div>

pageClickCounter.html

<div id="page-counter-child-view" style="border-radius: 10px; padding: 5px;"><h1>This is page counter, it will show the user click count number:</h1><h2>click: {{clickCount()}}</h2>
</div>

page1.ts

? import { Component } from '@angular/core';import { PageClickCounter } from '../pageClickCounter/pageClickCounter';@Component({selector: 'app-page1',imports: [PageClickCounter],templateUrl: './page1.html',styleUrl: './page1.less'})export class Page1 {}

pageClickCounter.ts

  import { Component, signal, AfterViewInit, OnDestroy } from '@angular/core';@Component({selector: 'page-click-counter',imports: [],templateUrl: './pageClickCounter.html',styleUrl: './pageClickCounter.less'})export class PageClickCounter implements AfterViewInit {protected clickCount = signal(0);childView: HTMLElement | null = null;ngAfterViewInit(): void {this.childView = document.querySelector('#page-counter-child-view');document.addEventListener('click', this.clickHandler);}clickHandler = () => {this.clickCount.update(count => count + 1);console.log('Page1 click count:', this.clickCount());// Update background color based on click count// Use HSL color with hue changing from green (120) to red (0) as clicks increaseconst hue = Math.min(120 - (this.clickCount() * 5), 120);(this.childView as HTMLElement).style.backgroundColor = `hsl(${hue}, 70%, 60%)`;};}

實際的運行效果如下:

這個Demo里已經存在泄露了,這里我們使用DevTools的堆分析器進行內存泄漏的檢測。

  • 點擊錄制,錄制好的快照如下:

  • 在搜索框輸入 detached 搜索分離的DOM樹節點:

看這個搜索結果的表,里面有四個列:

  • Constructor: 表示分離的DOM節點的構造類型
  • Distance: 節點與根節點的距離

在瀏覽器中GC回收的根節點就是window對象,其他的對象或者基本類型都是從這里出發鏈接到一起的。

  • Shallow size:這是對象本身持有的內存大小。典型的 JavaScript 對象會保留一些內存用于其描述和存儲即時值。通常,只有數組和字符串可以具有明顯的淺層大小。
  • Retained size:這是對象及其所有子對象所占的內存大小。更精確的描述是刪除對象本身及其無法從 GC 根訪問的依賴對象后釋放的內存大小。比如上圖中的節點6和8,節點8依賴節點6兒存在,如果節點6被刪除,那么節點8就無法訪問。

現在我們了解了快照表上的幾個列的含義,點擊一個行在下面的Retainer表和看到詳細的引用情況。然后我們就可以找到泄露產生的位置,來用對應的辦法解決。

但是看我們搜索出來的結果很雜亂,而且在實際的復雜項目中這個結果可能更加的復雜。那我們要從哪里開始下手呢?

其實在我們Angular或者Vue這些一組件為基礎組裝的應用中,如果我們從 <div> 或者 <h1> 這些節點開始向上找的話大概率會找到一個自定義的組件為止。

所以這里開始解決的小技巧是從大的組件開始解決,因為好多搜索出來的基礎元素泄露可能只是被自定義組件持有,當我們解決了組件級別的泄露,這些小的元素泄露會跟著消失。

好,看回我們的Demo在列表里發現了我們的自定義組件 page-click-counter,點擊進去

這里提示我們的 clickHandler 函數的引用關系,我們點擊進去

我們分析代碼,這里的childView持有了頁面上的元素,然后訂閱了document的click事件。問題出現在頁面銷毀的時候這個點擊事件的定義還在,clickHandler函數持有的DOM 對象childView就成為了分離的DOM。

好,我們開始解決這個問題。在頁面銷毀的生命周期函數里把訂閱取消。

// javascript
ngOnDestroy(): void {// Clean up the event listener to prevent memory leaksdocument.removeEventListener('click', this.clickHandler);
}

重新編譯運行,然后同樣的方法記錄內存快照。

我們可以看到,內存快照中不再有剛才的泄露對象。

五、?其他內存檢測方案

瀏覽器內置工具:

  • Chrome DevTools:
    • Heap Snapshot:對比多次快照,識別未釋放對象

  • Allocation Timeline:跟蹤內存分配時間線,定位泄漏點

六、?預防策略與未來方向

代碼規范:

  • 及時釋放資源:
    • 事件監聽器、定時器在 ngOnDestroy 中移除
// javascript
ngOnDestroy(): void {this.subscription.unsubscribe(); // 清理RxJS訂閱document.removeEventListener('click', this.handler);
}
  • 使用 WeakMap 替代強引用存儲臨時數據
// javascript    
// 使用WeakMap存儲臨時數據   
const weakMap = new WeakMap();   
const element = document.getElementById('target');   weakMap.set(element, { metadata: 'data' });   
// 當element被移除時,關聯數據可被GC回收
  • 避免全局變量:嚴格模式(use strict)禁用意外全局聲明

架構優化:

  • 資源隔離:為組件分配獨立作用域,避免交叉引用
// javascript
// Angular服務作用域隔離示例
@Injectable({ providedIn: 'root' }) // 根作用域
class RootService {}@Injectable({ providedIn: 'platform' }) // 平臺級作用域
class PlatformService {}@Injectable({ providedIn: 'any' }) // 每個模塊獨立實例
class ModuleService {}
  • 團隊實踐:定期代碼審查,重點關注事件綁定、閉包和第三方庫

審查項

檢查點

事件管理

是否在組件銷毀時移除監聽器

訂閱管理

RxJS訂閱是否正確取消

DOM引用

是否存在未釋放的DOM引用

緩存機制

是否設置過期策略

未來趨勢 現在我們可以借助AI大模型和MCP來實現文件掃描,讓大模型可以掃描并分析代碼查找可能出現內存泄露的寫法,并給出修改建議。

以下就是AI掃描pageClickCounter.ts文件后給出的修復建議。

The following memory leak issues were identified in the pageClickCounter.ts file:

1. Missing Event Listener Cleanup
The clickHandler event listener attached to document in ngAfterViewInit is not removed in ngOnDestroy.

This creates a reference cycle:

  • document retains a reference to clickHandler
  • clickHandler retains a reference to the component instance via this
  • Prevents Angular’s garbage collection of the component

2. Unmanaged DOM Reference
The childView property retains a direct DOM element reference through: javascript??

// javascript   this.childView = document.querySelector('#page-counter-child-view'); 

While less critical than the event listener issue, this could prolong DOM element lifecycle beyond component destruction if not explicitly nullified.

Recommended Fix:
Uncomment and implement ngOnDestroy to clean up resources:

// javascriptngOnDestroy(): void {// 1. Remove event listenerdocument.removeEventListener('click', this.clickHandler);// 2. Clear DOM referencethis.childView = null;}

Analysis Context:
- This is an Angular component using reactive signals (signal()) - The memory leak occurs through:
document -> clickHandler -> component -> childView -> DOM Element - The event handler pattern follows Angular’s best practices but requires explicit cleanup for document/window listeners

七、結論

與應用程序中的內存泄漏作斗爭可能是一項艱巨的任務。但是通過執行以上這些步驟,我們可以了解內存泄漏模式確定內存泄漏的根本原因,并實施必要的清理以防止進一步危害。而且現在我們現在有了LLM工具,我們可以使用大模型來分析代碼并確定內存泄漏的根本原因,并使這個自動化過程更加高效和有效。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86344.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86344.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86344.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在云服務器上搭建 MinIO 圖片存儲服務器及 Spring Boot 整合實現圖片上傳下載

一、MinIO 核心概念 MinIO 是一個高性能的分布式對象存儲服務器&#xff0c;兼容 Amazon S3 API&#xff0c;具有以下特點&#xff1a; 高性能&#xff1a;針對存儲和檢索優化 輕量級&#xff1a;單個二進制文件即可運行 云原生&#xff1a;支持 Kubernetes 部署 S3 兼容&a…

《深入解析:如何通過CSS集成WebGPU實現高級圖形效果》

當CSS的細膩筆觸遇上WebGPU的磅礴算力&#xff0c;兩者如同命運交織的織工&#xff0c;以代碼為絲線&#xff0c;在虛擬空間中編織出超越現實維度的靈境。這場融合不再局限于視覺呈現的革新&#xff0c;而是創造出一種能夠與用戶情感共鳴、突破物理法則束縛的沉浸式數字體驗&am…

R 語言科研繪圖 --- 環狀圖-匯總

在發表科研論文的過程中&#xff0c;科研繪圖是必不可少的&#xff0c;一張好看的圖形會是文章很大的加分項。 為了便于使用&#xff0c;本系列文章介紹的所有繪圖都已收錄到了 sciRplot 項目中&#xff0c;獲取方式&#xff1a; R 語言科研繪圖模板 --- sciRplothttps://mp.…

突破限制:實現頁面內精準監聽 localStorage 變更

突破限制&#xff1a;實現頁面內精準監聽 localStorage 變更 一、簡介二、示例演示三、StorageEvent重構setItem四、CustomEvent自定義事件同一頁面不同模塊數據同步五、MessageChannel同一頁面不同模塊數據同步六、BroadcastChannel多窗口數據同步七、CustomEventBroadcastCha…

牛客AI面試破解電銷招聘效率與成本雙重難題

在電銷行業&#xff0c;高流動性與大規模招聘需求長期困擾企業人力資源管理。傳統招聘模式下&#xff0c;HR需應對海量簡歷篩選、多輪面試協調、主觀評估偏差等挑戰&#xff0c;導致招聘周期長、成本高、人才匹配度低。如何通過技術手段實現精準篩選與效率提升&#xff1f;牛客…

智慧生產管控數字化平臺(源碼+文檔+講解+演示)

引言 在全球化和信息化的浪潮中&#xff0c;制造業正面臨著前所未有的挑戰和機遇。智慧生產管控數字化平臺應運而生&#xff0c;旨在通過數字化手段優化生產管控的全流程。本文將詳細介紹智慧生產管控數字化平臺的核心功能、技術架構以及如何通過開源代碼實現二次開發&#xf…

用Tensorflow進行線性回歸和邏輯回歸(九)

用TensorFlow訓練線性和邏輯回歸模型 這一節結合前面介紹的所有TensorFlow概念來訓練線性和邏輯回歸模型&#xff0c;使用玩具數據集。 用TensorFlow訓練模型 假如我們指明了數據點和標簽的容器&#xff0c;定義了張量操作的損失函數。添加了優化器節點到計算圖&#xff0c;…

使用 vue vxe-table 實現復選框禁用,根據行規則來禁用是否允許被勾選選中

使用 vue vxe-table 實現復選框禁用&#xff0c;根據行規則來禁用是否允許被勾選選中 查看官網&#xff1a;https://vxetable.cn 禁用選中 通過 checkMethod 方法控制 checkbox 是否允許用戶手動勾選&#xff0c;如果被禁用&#xff0c;可以調用 setCheckboxRow 方法手動設置…

【Linux-網絡】深入拆解TCP核心機制與UDP的無狀態設計

&#x1f3ac; 個人主頁&#xff1a;誰在夜里看海. &#x1f4d6; 個人專欄&#xff1a;《C系列》《Linux系列》《算法系列》 ?? 道阻且長&#xff0c;行則將至 目錄 &#x1f4da;引言 &#x1f4da;一、UDP協議 &#x1f4d6; 1.概述 &#x1f4d6; 2.特點 &#x1…

(nice!!!)(LeetCode 每日一題) 2081. k 鏡像數字的和 (枚舉)

題目&#xff1a;2081. k 鏡像數字的和 思路&#xff1a;枚舉10進制的回文串&#xff0c;然后來判斷對應的k進制數是否是回文串。直到有n個滿意即可。 而枚舉10進制的回文串&#xff0c;從基數p(1、10、100… )開始&#xff0c;長度為奇數的回文串&#xff0c;長度為偶數的回文…

Java面試題027:一文深入了解數據庫Redis(3)

Java面試題025&#xff1a;一文深入了解數據庫Redis&#xff08;1&#xff09; Java面試題026&#xff1a;一文深入了解數據庫Redis&#xff08;2&#xff09; 本節我們整理一下Redis高可用和消息隊列使用場景的重點原理&#xff0c;讓大家在面試或者實際工作中遇到這類問題時…

算法打卡 day4

4 . 高精度算法 性質&#xff1a;數組或者容器從低位往高位依次存儲大整數&#xff0c;方便進位。 4.1 高精度加法 給定兩個正整數&#xff08;不含前導 0&#xff09;&#xff0c;計算它們的和。 輸入格式 共兩行&#xff0c;每行包含一個整數。 輸出格式 共一行&#xff0c;…

【筆記】Docker 配置阿里云鏡像加速(公共地址即開即用,無需手動創建實例)

2025年06月25日記 【好用但慎用】Windows 系統中將所有 WSL 發行版從 C 盤遷移到 非系統 盤的完整筆記&#xff08;附 異常處理&#xff09;-CSDN博客 【筆記】解決 WSL 遷移后 Docker 出現 “starting services: initializing Docker API Proxy: setting up docker ap” 問題…

day35-Django(1)

day35-Django 3.2 前言 之前我們介紹過web應用程序和http協議,簡單了解過web開發的概念。Web應用程序的本質 接收并解析HTTP請求,獲取具體的請求信息處理本次HTTP請求,即完成本次請求的業務邏輯處理構造并返回處理結果——HTTP響應import socketserver = socket.socket() …

PostgreSQL全棧部署指南:從零構建企業級高可用數據庫集群

PostgreSQL全棧部署指南:從零構建企業級數據庫集群 前言: 本文詳解了**PostgreSQL**所有的部署方式,如 yum 安裝、源碼編譯安裝、RPM包手動安裝,以及如何選擇適合的安裝方式。適合不同的場景應用。通過高可用部署詳細了解安裝思路及過程,包括內網環境下的配置、主節點的創…

MQTT 和 HTTP 有什么本質區別?

MQTT 和 HTTP 的本質區別在于它們設計的初衷和核心工作模式完全不同。它們是為解決不同問題而創造的兩種工具。 簡單來說&#xff1a; HTTP 就像是去圖書館問問題&#xff1a;你&#xff08;客戶端&#xff09;主動去找圖書管理員&#xff08;服務器&#xff09;&#xff0c;…

GtkSharp跨平臺WinForm實現

文章目錄 跨平臺架構設計跨平臺項目配置GtkSharp串口通訊實現跨平臺部署配置Linux系統配置macOS系統配置 相關學習資源GTK#跨平臺開發跨平臺.NET開發Linux開發環境macOS開發環境跨平臺UI框架對比容器化部署開源項目參考性能優化與調試 跨平臺架構設計 基于GTKSystem.Windows.F…

【閑談】對于c++未來的看法

對于C未來看法 C 作為一門誕生于上世紀的編程語言&#xff0c;在軟件工業發展史上扮演了不可替代的角色。盡管近年來諸如 Rust、Go、Swift、Kotlin 等現代語言相繼崛起&#xff0c;C 依然在系統軟件、高性能服務、嵌入式等關鍵領域中發揮著主力作用。本文將從 C 的當前應用前景…

【論文】云原生事件驅動架構在智能風控系統中的實踐與思考

摘要 2023年6月至2024年3月,我作為某頭部證券公司新一代極速交易系統的首席架構師,主導設計并落地了基于云原生事件驅動架構的全新交易風控平臺。該項目旨在攻克原有系統無法支撐峰值20萬筆/秒交易量、風控延遲超過3秒以及行情劇烈波動時系統崩潰等核心痛點。通過構建以Kube…

opensbi從0到1入門學習

最近要在RV64的平臺上把Linux給bringup起來&#xff0c;由于當下的工作主要集中在底層硬件接口驅動、CPU的操作及RTOS應用等&#xff0c;雖然之前搞過Arm Linux的開發工作&#xff0c;但是比較基礎的玩的比較少&#xff0c;所以真正要搞把系統bringup起來&#xff0c;我之前的知…