成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

JavaScript 內存泄漏防范之道

開發 前端
一般情況下,忽視內存管理不會對傳統的網頁產生顯著的后果。這是因為,用戶刷新頁面后,內存數據都被清理了。

 一般情況下,忽視內存管理不會對傳統的網頁產生顯著的后果。這是因為,用戶刷新頁面后,內存數據都被清理了。

[[329247]]

但是隨著SPA(單頁應用)的普及,我們不得不更加關注頁面的內存管理。用戶在 SPA 上往往很少刷新頁面,隨著頁面停留時間的增長,內存可能越占越多,輕則影響頁面性能,嚴重的可能導致標簽頁崩潰。

在這篇文章中,我們將探討導致 JavaScript 中內存泄露的常見原因,以及如何改善內存管理。

瀏覽器將對象保留在堆內存中,通過引用鏈可從根對象到達這些對象。垃圾回收器(GC)是 JavaScript 引擎中的一個后臺進程,它可以識別無法到達的對象,將其刪除,并回收相應的內存。

 

引用鏈 - GC - 對象關系圖

當內存中本應在垃圾回收循環中被清理的對象,通過另一個對象意外的引用從而維持可訪問狀態,就會發生內存泄漏。將多余的對象保持在內存中,會導致應用程序內部的內存使用量過大,進而影響性能。

 

內存泄露

如何判斷代碼是否存在內存泄漏呢?內存泄漏通常比較隱蔽,難以發現和定位。造成內存泄漏的 JavaScript 代碼看上去挺正常,瀏覽器在運行的時候也不會拋出錯誤。如果發現頁面性能越來越差,通常是內存泄漏的征兆,可以通過瀏覽器內置的工具判斷是否存在內存泄漏,并分析出原因。

最快的方法是查看瀏覽器的任務管理器(注意,不是操作系統的任務管理器)。它提供了瀏覽器運行中的所有 tab 頁和進程的資源使用情況,比如內存占用、CPU 占用和進程 ID 等。Chrome 的任務管理器可通過 Shift+Esc 快捷鍵打開,Firefox 可在地址欄輸入about:performance打開。

如果頁面都沒有任何交互,內存占用卻越來越多,很可能存在泄漏。

  

Chrome 任務管理器

瀏覽器 DevTools 則提供了更豐富的內存管理功能。可以在 Chrome 的性能面板錄制頁面運行情況,查看可視化的性能分析數據。

 

Chrome 性能面板

除此之外,Chrome 和 Firefox 的 DevTools 還有專門的內存工具用于分析內存使用情況。通過比較連續的內存快照,可以看出內存分配情況。

通過前面的分析,內存泄露的根本原因就是代碼在無意之中引用了本該被 GC 回收的對象。那么,哪些情況容易造成內存泄露呢?

1、意外的全局變量

全局變量一直處于可訪問狀態,不會被 GC 回收。在非嚴格模式下,有時會不小心讓局部變量變成全局變量。

  • 給未聲明的變量賦值
  • 使用指向全局對象的 this
  1. function createGlobalVariables() { 
  2.     leaking1 = '變成全局變量了'; // 給未聲明的變量賦值 
  3.     this.leaking2 = '這也是全局變量'; // 'this' 指向全局對象 
  4. }; 
  5. createGlobalVariables(); 
  6. window.leaking1; // '變成全局變量了' 
  7. window.leaking2; // '這也是全局變量' 

如何避免: 嚴格模式 ("use strict") 會避免意外的全局變量,以上代碼在嚴格模式下會報錯。

2、閉包

函數作用域變量在函數執行完后會被清理,前提是在函數外部沒有引用它。閉包會讓變量一直處于被引用狀態,即使它的執行上下文和作用域已經不存在了。

  1. function outer() { 
  2.     const Array = []; 
  3.     return function inner() { 
  4.         bigArray.push('Hello');  
  5.         console.log('Hello'); 
  6.     }; 
  7. }; 
  8. const sayHello = outer(); // 包含了對 inner 的引用 
  9.  
  10. function repeat(fn, num) { 
  11.     for (let i = 0; i < num; i++){ 
  12.         fn(); 
  13.     } 
  14. repeat(sayHello, 10); // 每次調用 sayHello 都會添加 'Hello' 到potentiallyHugeArray  
  15.  
  16. // 如果是10萬次呢?闊怕:repeat(sayHello, 100000) 

上面例子中的數組 bigArray 沒有從任何函數中直接返回,因此無法直接訪問,但是它卻不停地膨脹,取決于我們調用了多少次 function inner()。

如何避免: 閉包是 JavaScript 語言的特性之一,如果無法避開,那就請注意兩點:

  • 清楚閉包是何時創建的,以及哪些對象會被保留在內存中;
  • 清楚閉包的生命周期和用途(尤其是當做回調函數的時候)

3、定時器

在 setTimeout 或 setInterval 的回調函數中引用某些對象,是防止被 GC 回收的常見做法。如果在代碼里設置循環定時器(setTimeout也能像setInterval一樣定時重復執行,只要設置成遞歸調用),只要定時器還在運行,回調函數中的對象就會一直保持在內存中。

下面的例子中,data 對象會在清除定時器后被 GC 回收。但我們沒有獲取 setInterval的返回值,也就沒辦法用代碼清除這個定時器,因此盡管完全沒有用到,data.hugeString 也會一直保留在內存中,直到進程結束。

  1. function setCallback() { 
  2.     const data = { 
  3.         counter: 0, 
  4.         hugeString: new Array(100000).join('x'
  5.     }; 
  6.     return function cb() { 
  7.         data.counter++; // data 對象現在已經屬于回調函數的作用域了 
  8.         console.log(data.counter); 
  9.     } 
  10. setInterval(setCallback(), 1000); // 沒法停止定時器了 

如何避免: 對于生命周期不確定的回調函數,我們應該:

  • 注意被定時器回調函數引用的對象
  • 使用定時器返回的句柄,在必要時清除它

也可以通過分離變量的方式,避免對大對象的引用:

  1. function setCallback() { 
  2.     // 分開定義變量 
  3.     let counter = 0; 
  4.     const hugeString = new Array(100000).join('x'); // setCallback執行完即可被回收 
  5.     return function cb() { 
  6.         counter++; // 只剩 counter 位于回調函數作用域 
  7.         console.log(counter); 
  8.     } 
  9.  
  10. const timerId = setInterval(setCallback(), 1000); // 保存定時器 ID 
  11.  
  12. // 執行某些操作 ... 
  13.  
  14. clearInterval(timerId); // 停止定時器 

4、事件監聽器

活動的事件監聽器會阻止作用域內的變量被 GC 回收。事件監聽器一直處于活動狀態,直到用 removeEventListener() 顯式移除,或者關聯的 DOM 元素被移除。

對于有些事件來說,監聽器需要一直保留,直到頁面被銷毀。比如按鈕點擊事件,我們可能需要重復使用。但是,有時候我們希望某個事件只執行特定次數。

  1. const hugeString = new Array(100000).join('x'); 
  2. document.addEventListener('keyup'function() { // 匿名監聽器無法移除 
  3.     doSomething(hugeString); // hugeString 會一直處于回調函數的作用域內 
  4. }); 

上面例子中的事件監聽器用了匿名函數,這樣就沒法用removeEventListener()移除了。同時,document元素也無法刪除,因此事件回調函數內的變量會一直保留,哪怕我們只想觸發一次事件。

如何避免: 事件監聽器不再需要時,要記得解除綁定。使用具名函數方式獲取引用,通過removeEventListener()解除綁定。

  1. function listener() { 
  2.     doSomething(hugeString); 
  3. document.addEventListener('keyup', listener);  
  4. document.removeEventListener('keyup', listener);  

如果事件監聽器只需要執行一次, addEventListener()可以接受第三個參數,是一個配置對象。指定{once: true},監聽器函數會在事件觸發一次執行后自動移除(匿名函數也可以)。

  1. document.addEventListener('keyup'function listener(){ 
  2.     doSomething(hugeString); 
  3. }, {once: true}); // 執行一次后自動移除事件監聽器 

5、緩存

如果持續不斷地往緩存里增加數據,沒有定時清除無用的對象,也沒有限制緩存大小,那么緩存就會像滾雪球一樣越來越大。

  1. let user_1 = { name"Kayson", id: 12345 }; 
  2. let user_2 = { name"Jerry", id: 54321 }; 
  3. const mapCache = new Map(); 
  4.  
  5. function cache(obj){ 
  6.     if (!mapCache.has(obj)){ 
  7.         const value = `${obj.name} has an id of ${obj.id}`; 
  8.         mapCache.set(obj, value); 
  9.  
  10.         return [value, 'computed']; 
  11.     } 
  12.  
  13.     return [mapCache.get(obj), 'cached']; 
  14.  
  15. cache(user_1); // ['Kayson has an id of 12345''computed'
  16. cache(user_1); // ['Kayson has an id of 12345''cached'
  17. cache(user_2); // ['Jerry has an id of 54321''computed'
  18.  
  19. console.log(mapCache); // ((…) => "Kayson has an id of 12345", (…) => "Jerry has an id of 54321"
  20. user_1 = null;  
  21.  
  22. //Garbage Collector 
  23. console.log(mapCache); // ((…) => "Kayson has an id of 12345", (…) => "Jerry has an id of 54321") // 依然在緩存里 

上面的例子中,緩存依然保留了user_1 的數據。因此我們需要把不再使用的數據從緩存中刪除。

可能的解決方案: 為了解決這個問題,可以使用 WeakMap。 WeakMap 是一種數據結構,它只用對象作為鍵,并保持對象鍵的弱引用,如果這個對象被置空了,相關的鍵值對會被 GC 自動回收。

  1. let user_1 = { name"Kayson", id: 12345 }; 
  2. let user_2 = { name"Jerry", id: 54321 }; 
  3. const weakMapCache = new WeakMap(); 
  4.  
  5. function cache(obj){ 
  6.     // 代碼跟前一個例子相同,只不過用的是 weakMapCache 
  7.  
  8.     return [weakMapCache.get(obj), 'cached']; 
  9.  
  10. cache(user_1); // ['Kayson has an id of 12345''computed'
  11. cache(user_2); // ['Jerry has an id of 54321''computed'
  12. console.log(weakMapCache); // ((…) => "Kayson has an id of 12345", (…) => "Jerry has an id of 54321"
  13. user_1 = null;  
  14.  
  15. // Garbage Collector 
  16.  
  17. console.log(weakMapCache); // ((…) => "Jerry has an id of 54321") - 第一條記錄已被 GC 刪除 

6、分離的 DOM 元素

如果 DOM 節點被 JavaScript 代碼直接引用,即使從 DOM 樹分離,也不會被 GC 回收。

下面的例子中,removeChild() 達不到預期效果,堆快照會顯示HTMLDivElement處于分離狀態,因為有個變量指向了這個div。

  1. function createElement() { 
  2.     const div = document.createElement('div'); 
  3.     div.id = 'detached'
  4.     return div; 
  5.  
  6. // 即使調用了deleteElement() ,依然保存著 DOM 元素的引用 
  7. const detachedDiv = createElement(); 
  8. document.body.appendChild(detachedDiv); 
  9. function deleteElement() { 
  10. document.body.removeChild(document.getElementById('detached')); 
  11.  
  12. deleteElement(); // 堆快照顯示: detached div#detached 

如何避免: 一種方法是把DOM 引用限制為局部作用域。

  1. function createElement() {...} //  
  2. // DOM 引用位于函數作用域內 
  3.  
  4. function appendElement() { 
  5.     const detachedDiv = createElement(); 
  6.     document.body.appendChild(detachedDiv); 
  7.  
  8. appendElement(); 
  9.  
  10. function deleteElement() { 
  11.      document.body.removeChild(document.getElementById('detached')); 
  12.  
  13. deleteElement(); 

總結

對于重要的前端應用,定位和解決 JavaScript 內存問題是一項頗具挑戰性的任務。因此,理解典型的內存泄露原因,從而在源頭上避免,是做好內存管理的必要工作。希望本文總結的造成內存泄漏的六大來源對你有所啟發,在寫代碼的時候有所防范。

 

責任編輯:華軒 來源: 大道至簡
相關推薦

2025-05-06 07:24:24

2021-08-05 15:28:22

JS內存泄漏

2013-01-22 11:31:00

2009-06-10 22:03:40

JavaScript內IE內存泄漏

2022-05-26 09:51:50

JavaScrip內存泄漏

2010-07-16 09:11:40

JavaScript內存泄漏

2012-04-06 10:04:21

2021-02-26 00:49:00

DMARC郵件安全信息泄漏

2022-09-28 10:35:31

JavaScript代碼內存泄漏

2023-02-20 15:27:30

開發JavaScript內存管理

2014-07-21 14:40:43

Android內存

2014-07-28 15:01:56

Android內存

2024-01-15 16:28:42

ChatGPTGenAI人工智能

2016-05-17 09:42:16

2022-08-31 12:15:09

JavaScript代碼優化

2024-03-11 08:22:40

Java內存泄漏

2023-12-18 10:45:23

內存泄漏計算機服務器

2012-02-22 21:28:58

內存泄漏

2014-07-31 10:48:09

Android內存管理OOM

2015-03-30 11:18:50

內存管理Android
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 狠狠干影院 | 伊人久久国产 | 亚洲一区二区三区国产 | 色婷婷av99xx| 国产精品久久久久久久久免费高清 | 久久久久精 | 国产高清一区二区三区 | 久久国产精品免费一区二区三区 | 亚洲三级视频 | 国产精品成人一区二区 | 精品美女久久久久久免费 | 欧美一级黄色片 | 91精品国产综合久久福利软件 | 最近最新中文字幕 | 羞羞视频在线网站观看 | 国产精品一区二区av | 亚洲一区二区久久 | 网站黄色av | 午夜午夜精品一区二区三区文 | 日韩伦理一区二区三区 | 一级毛毛片 | 99国产精品视频免费观看一公开 | 色欧美综合 | 精品视频在线观看 | 99tv成人影院 | 91视频网址 | 亚洲日本成人 | 麻豆av在线 | 精品中文字幕在线观看 | 91麻豆产精品久久久久久 | 精品自拍视频在线观看 | 麻豆一区一区三区四区 | 欧美日韩国产一区二区三区 | 亚州无限乱码 | 麻豆亚洲 | 91久久北条麻妃一区二区三区 | 国产观看| 欧美一区二区三区精品 | 精品一区二区三区免费毛片 | 久久综合狠狠综合久久综合88 | 精品久久精品 |