亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:京東零售 謝天

在任何語言開發的過程中,對于內存的管理都非常重要,JAVAscript 也不例外。

然而在前端瀏覽器中,用戶一般不會在一個頁面停留很久,即使有一點內存泄漏,重新加載頁面內存也會跟著釋放。而且瀏覽器也有自己的自動回收內存的機制,所以前端并沒有特別關注內存泄漏的問題。

但是如果我們對內存泄漏沒有什么概念,有時候還是有可能因為內存泄漏,導致頁面卡頓。了解內存泄漏,如何避免內存泄漏,都是不可缺少的。

什么是內存

在硬件級別上,計算機內存由大量觸發器組成。每個觸發器包含幾個晶體管,能夠存儲一個位。單個觸發器可以通過唯一標識符尋址,因此我們可以讀取和覆蓋它們。因此,從概念上講,我們可以把我們的整個計算機內存看作是一個巨大的位數組,我們可以讀和寫。

 

這是內存的底層概念,JavaScript 作為一個高級語言,不需要通過二進制進行內存的讀寫,而是相關的 JavaScript 引擎做了這部分的工作。

內存的生命周期

內存也會有生命周期,不管什么程序語言,一般可以按照順序分為三個周期:

 

  • 分配期:分配所需要的內存
  • 使用期:使用分配的內存進行讀寫
  • 釋放期:不需要時將其釋放和歸還

 

內存分配 -> 內存使用 -> 內存釋放

什么是內存泄漏

在計算機科學中,內存泄漏指由于疏忽或錯誤造成程序未能釋放已經不再使用的內存。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。

 

如果內存不需要時,沒有經過生命周期的的釋放期,那么就存在內存泄漏

內存泄漏的簡單理解:無用的內存還在占用,得不到釋放和歸還。比較嚴重時,無用的內存會持續遞增,從而導致整個系統的卡頓,甚至崩潰。

JavaScript 內存管理機制

像 C 語言這樣的底層語言一般都有底層的內存管理接口,但是 JavaScript 是在創建變量時自動進行了內存分配,并且在不使用時自動釋放,釋放的過程稱為“垃圾回收”。然而就是因為自動回收的機制,讓我們錯誤的感覺開發者不必關心內存的管理。

JavaScript 內存管理機制和內存的生命周期是一致的,首先需要分配內存,然后使用內存,最后釋放內存。絕大多數情況下不需要手動釋放內存,只需要關注對內存的使用(變量、函數、對象等)。

內存分配

JavaScript 定義變量就會自動分配內存,我們只需要了解 JavaScript 的內存是自動分配的就可以了。

let num = 1; const str = "名字"; const obj = { a: 1, b: 2 } const arr = [1, 2, 3]; function func (arg) { ... } 內存使用

使用值的過程實際上是對分配的內存進行讀寫的操作,讀取和寫入的操作可能是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。

// 繼續上部分 // 寫入內存 num = 2; // 讀取內存,寫入內存 func(num); 內存回收

垃圾回收被稱為 GC(Garbage Collection)

內存泄漏一般都是發生在這一步,JavaScript 的內存回收機制雖然可以回收絕大部分的垃圾內存,但是還是存在回收不了的情況,如果存在這些情況,需要我們自己手動清理內存。

以前一些老版本的瀏覽器的 JavaScript 回收機制沒有那么完善,經常出現一些 bug 的內存泄漏,不過現在的瀏覽器一般都沒有這個問題了。

這里了解下現在 JavaScript 的垃圾內存的兩種回收方式,熟悉一下這兩種算法可以幫助我們理解一些內存泄漏的場景。

引用計數

這是最初級的垃圾收集算法。此算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。

// “對象”分配給 obj1 var obj1 = { a: 1, b: 2 } // obj2 引用“對象” var obj2 = obj1; // “對象”的原始引用 obj1 被 obj2 替換 obj1 = 1;

當前執行環境中,“對象”內存還沒有被回收,需要手動釋放“對象”的內存(在沒有離開當前執行環境的前提下)

obj2 = null; // 或者 obj2 = 1; // 只要替換“對象”就可以了

這樣引用的“對象”內存就被回收了。

ES6 中把引用分為強引用和弱引用,這個目前只有在 Set 和 Map 中才存在。

強引用才會有引用計數疊加,只有引用計數為 0 的對象的內存才會被回收,所以一般需要手動回收內存(手動回收的前提在于標記清除法還沒執行,還處于當前的執行環境)。

而弱引用沒有觸發引用計數疊加,只要引用計數為 0,弱引用就會自動消失,無需手動回收內存。

標記清除

當變量進入執行時標記為“進入環境”,當變量離開執行環境時則標記為“離開環境”,被標記為“進入環境”的變量是不能被回收的,因為它們正在被使用,而標記為“離開環境”的變量則可以被回收。

環境可以理解為我們的執行上下文,全局作用域的變量只會在頁面關閉時才會被銷毀。

// 假設這里是全局上下文 var b = 1; // b 標記進入環境 function func() { var a = 1; return a + b; // 函數執行時,a 被標記進入環境 } func(); // 函數執行結束,a 被標記離開環境,被回收 // 但是 b 沒有標記離開環境 JavaScript 內存泄漏的一些場景

JavaScript 的內存回收機制雖然能回收絕大部分的垃圾內存,但是還是存在回收不了的情況。程序員要讓瀏覽器內存泄漏,瀏覽器也是管不了的。

下面有些例子是在執行環境中,沒離開當前執行環境,還沒觸發標記清除法。所以你需要讀懂上面 JavaScript 的內存回收機制,才能更好的理解下面的場景。

意外的全局變量 // 在全局作用域下定義 function count(num) { a = 1; // a 相當于 window.a = 1; return a + num; }

不過在 eslint 幫助下,這種場景現在基本沒人會犯了,eslint 會直接報錯,了解下就好。

遺忘的計時器

無用的計時器忘記清理,是最容易犯的錯誤之一。

拿一個 vue 組件舉個例子。

上面的組件銷毀的時候,setInterval 還是在運行的,里面涉及到的內存都是沒法回收的(瀏覽器會認為這是必須的內存,不是垃圾內存),需要在組件銷毀的時候清除計時器。

遺忘的事件監聽

無用的事件監聽器忘記清理也是最容易犯的錯誤之一。

還是使用 vue 組件舉個例子。

上面的組件銷毀的時候,resize 事件還是在監聽中,里面涉及到的內存都是沒法回收的,需要在組件銷毀的時候移除相關的事件。

遺忘的 Set 結構

Set 是 ES6 中新增的數據結構,如果對 Set 不熟,可以看這里。

如下是有內存泄漏的(成員是引用類型,即對象):

let testSet = new Set(); let value = { a: 1 }; testSet.add(value); value = null;

需要改成這樣,才會沒有內存泄漏:

let testSet = new Set(); let value = { a: 1 }; testSet.add(value); testSet.delete(value); value = null;

有個更便捷的方式,使用 WeakSet,WeakSet 的成員是弱引用,內存回收不會考慮這個引用是否存在。

let testSet = new WeakSet(); let value = { a: 1 }; testSet.add(value); value = null; 遺忘的 Map 結構

Map 是 ES6 中新增的數據結構,如果對 Map 不熟,可以看這里。

如下是有內存泄漏的(成員是引用類型,即對象):

let map = new Map(); let key = [1, 2, 3]; map.set(key, 1); key = null;

需要改成這樣,才會沒有內存泄漏:

let map = new Map(); let key = [1, 2, 3]; map.set(key, 1); map.delete(key); key = null;

有個更便捷的方式,使用 WeakMap,WeakMap 的鍵名是弱引用,內存回收不會考慮到這個引用是否存在。

let map = new WeakMap(); let key = [1, 2, 3]; map.set(key, 1); key = null 遺忘的訂閱發布

和上面事件監聽器的道理是一樣的。

建設訂閱發布事件有三個方法,emit、on、off 三個方法。

還是繼續使用 vue 組件舉例子:

 

上面組件銷毀的時候,自定義 test 事件還是在監聽中,里面涉及到的內存都是沒辦法回收的,需要在組件銷毀的時候移除相關的事件。


遺忘的閉包

閉包是經常使用的,閉包能提供很多的便利,

首先看下下面的代碼:

function closure() { const name = '名字'; return () => { return name.split('').reverse().join(''); } } const reverseName = closure(); reverseName(); // 這里調用了 reverseName

上面有沒有內存泄漏?是沒有的,因為 name 變量是要用到的(非垃圾),這也是從側面反映了閉包的缺點,內存占用相對高,數量多了會影響性能。

但是如果 reverseName 沒有被調用,在當前執行環境未結束的情況下,嚴格來說,這樣是有內存泄漏的,name 變量是被 closure 返回的函數調用了,但是返回的函數沒被使用,在這個場景下 name 就屬于垃圾內存。name 不是必須的,但是還是占用了內存,也不可被回收。

當然這種也是極端情況,很少人會犯這種低級錯誤。這個例子可以讓我們更清楚的認識內存泄漏。

DOM 的引用

每個頁面上的 DOM 都是占用內存的,建設有一個頁面 A 元素,我們獲取到了 A 元素 DOM 對象,然后賦值到了一個變量(內存指向是一樣的),然后移除了頁面上的 A 元素,如果這個變量由于其他原因沒有被回收,那么就存在內存泄漏,如下面的例子:

class Test { constructor() { this.elements = { button: document.querySelector('#button'), div: document.querySelector('#div') } } removeButton() { document.body.removeChild(this.elements.button); // this.elements.button = null } } const test = new Test(); test.removeButton();

上面的例子 button 元素雖然在頁面上移除了,但是內存指向換成了 this.elements.button,內存占用還是存在的。所以上面的代碼還需要這么寫:this.elements.button = null,手動釋放內存。

如何發現內存泄漏

內存泄漏時,內存一般都是周期性的增長,我們可以借助谷歌瀏覽器的開發者工具進行判斷。

這里針對下面的例子進行一步步的的排查和找到問題點:

運行 停止

確實是否是內存泄漏問題

訪問上面的代碼頁面,打開開發者工具,切換至 Performance 選項,勾選 Memory 選項。

在頁面上點擊運行按鈕,然后在開發者工具上面點擊左上角的錄制按鈕,10 秒后在頁面上點擊停止按鈕,5 秒停止內存錄制。得到內存走勢如下:


 

由上圖可知,10 秒之前內存周期性增長,10 秒后點擊了停止按鈕,內存平穩,不再遞增。我們可以使用內存走勢圖判斷是否存在內存泄漏。

查找內存泄漏的位置

上一步確認內存泄漏問題后,我們繼續利用開發者工具進行問題查找。

訪問上面的代碼頁面,打開開發者工具,切換至 Memory 選項。頁面上點擊運行按鈕,然后點擊開發者工具左上角的錄制按鈕,錄制完成后繼續點擊錄制,直到錄制完成三個為止。然后點擊頁面上的停止按鈕,在連續錄制三次內存(不要清理之前的錄制)。


 

從這里也可以看出,點擊運行按鈕之后,內存在不斷的遞增。點擊停止按鈕之后,內存就平穩了。雖然我們也可以用這種方式來判斷是否存在內存泄漏,但是沒有第一步的方法便捷,走勢圖也更加直觀。

然后第二步的主要目的是為了記錄 JavaScript 堆內存,我們可以看到哪個堆占用的內存更高。


 

從內存記錄中,發現 array 對象占用最大,展開后發現,第一個 object elements 占用最大,選擇這個 object elements 后可以在下面看到 newArr 變量,然后點擊后面的高亮鏈接,就可以跳轉到 newArr 附近。

分享到:
標簽:JavaScript
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定