天天看點

javaScript 中的垃圾回收和記憶體洩漏 

  • 2018/11/05 - 兩種方案實作小程式動畫
  • 2018/11/05 - webpack 原理與實踐(一):打包流程
  • 2018/11/05 - React和Vue中,是如何監聽變量變化的
  • 2018/10/26 -【React源碼解讀】- 元件的實作
  • 2018/10/23 - 小程式雲開發初探
  • 2018/10/15 -【從前端到全棧】- koa快速入門指南
  • 2018/10/15 - 前端骨架屏方案小結
  • 2019/09/25 - 你應該知道的相對路徑與絕對路徑
  • 2019/09/18 - 三大圖表庫:ECharts 、 BizCharts 和 G2,該如何選擇
  • 2018/09/09 - 不可或缺的正則手冊
  • 2018/09/07 -【React 實戰教程】從0到1 建構 github star管理工具
  • 2018/09/04 - 7分鐘了解JS的節流、防抖及使用場景
  • 2018/08/13 - 前端可視化建構工具——Vue UI & 阿裡飛冰
  • 2018/08/06 - chrome devtools 官方文檔閱讀筆記(十分鐘上手performance面闆的基本使用)
  • 2018/07/29 - 微信小程式如何使用iconfont?
  • 2018/07/22 - Vue-cli原理分析
  • 2018/07/15 - JavaScript中的垃圾回收和記憶體洩漏
  • 2018/07/08 - 異常處理,"try..catch"(譯)
  • 2018/07/06 - 淺談web前端的發展趨勢
  • 2018/06/24 - 從0開始釋出一個無依賴、高品質的npm
  • 2018/06/16 - 結合 Vue 源碼談談釋出訂閱模式
  • 2018/06/11 - 前端項目性能優化之打包工具篇
  • 2018/06/08 - 10分鐘了解JS堆、棧以及事件循環的概念
  • 2018/06/07 - 低門檻徹底了解JavaScript中的深拷貝和淺拷貝

之前接觸的js的記憶體管理方面的内容一直比較零散,最近在這一塊做了一些系統的學習.學習過程中的一些總結在這裡分享給大家.歡迎批評指正,共同學習,共同進步.

在一部分語言中是提供了記憶體管理的接口的,例如C語言中的 

molloc()

和 

free()

; 而在 

JavaScript

 中會自動進行記憶體的配置設定和回收的,因為自動這兩個字,就讓很多的開發者認為我們是不需要去關心記憶體方面的問題,當然,這是一種錯誤的看法.關注記憶體的管理,避免記憶體的洩漏也是性能優化重要的一項.

變量的生命周期

Javascript

 變量的生命周期要分開來看,對于全局變量,他的生命周期會持續到頁面關閉(這就涉及到了後面要總結的記憶體洩漏的一種方式).而對于局部變量,在所在的函數的代碼執行之後,局部變量的生命周期結束,他所占用的記憶體會通過垃圾回收機制釋放(即垃圾回收).

垃圾回收機制

垃圾回收通常有兩種方式來實作:

引用計數

這種算法在

MDN

文檔中被稱為最"天真"的垃圾回收算法;核心原理是: 判斷一個對象是否要被回收就是要看是否還有引用指向它,如果是"零引用",那麼就回收.說這種算法天真,是因為它存在着較為嚴重的缺陷---循環引用:

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

           

首先要注意我們是在函數作用域中讨論的這個問題,而不是全局環境中.老版本的IE中的非

JavaScript

原生對象如 

DOM

 和 

BOM

 對象就采用的這種政策.下面這種情況下就會出現記憶體洩漏:

var el =document.getElementById("some_element");
var Obj =new Object();
myObj.el = el;
el.someObject = Obj;

           

當然我們可以在不用的時候手動釋放:

myObj.el = null;
el.someObject = null;

           

标記清除

這個算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”.

這個算法假定有一個根(root)的對象;在 

Javascript

 裡,根是全局對象,對應于浏覽器環境的 

window

,

node

 環境的 

global

.垃圾回收器将定期從根開始,找所有從根開始引用的對象,然後找這些對象引用的對象……從根開始,垃圾回收器将找到所有可以獲得的對象和收集所有不能獲得的對象.

這個算法相對于引用計數的優勢在于,“有零引用的對象”總是不可獲得的,但是相反卻不一定,參考“循環引用”.

從2012年起,所有現代浏覽器都使用了标記-清除垃圾回收算法,都是在此基礎上進行優化.所有對JavaScript垃圾回收算法的改進都是基于标記-清除算法的改進,并沒有改進标記-清除算法本身和它對“對象是否不再需要”的簡化定義.

限制: 那些無法從根對象查詢到的對象都将被清除

當然,在我們的開發實踐中很少遇到這種情況,這也是我們忽略記憶體管理的原因之一.

常見的記憶體洩漏舉例

1.忘記聲明的局部變量

function a(){
    b=2
    console.log('b沒有被聲明!')
}
           

b

 沒被聲明,會變成一個全局變量,在頁面關閉之前不會被釋放.使用嚴格模式可以避免.

2.閉包帶來的記憶體洩漏

var leaks = (function(){
    var leak = 'xxxxxx';// 閉包中引用,不會被回收
    return function(){
        console.log(leak);
    }
})()

           

當然有時候我們是故意讓這個變量儲存在記憶體中的,但是要避免無意的時候造成的記憶體洩漏.

3.移除 

DOM

 節點時候忘記移除暫存的值

有時候出于優化性能的目的,我們會用一個變量暫存 

節點

,接下來使用的時候就不用再從 

DOM

 中去擷取.但是在移除 

DOM

 節點的時候卻忘記了解除暫存的變量對 

DOM

 節點的引用,也會造成記憶體洩漏

var element = {
  image: document.getElementById('image'),
  button: document.getElementById('button')
};

document.body.removeChild(document.getElementById('image'));
// 如果element沒有被回收,這裡移除了 image 節點也是沒用的,image 節點依然留存在記憶體中.

           

與此類似情景還有: 

DOM

 節點綁定了事件, 但是在移除的時候沒有解除事件綁定,那麼僅僅移除 

DOM

 節點也是沒用的

4. 定時器中的記憶體洩漏

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
           

如果沒有清除定時器,那麼 

someResource

 就不會被釋放,如果剛好它又占用了較大記憶體,就會引發性能問題. 再提一下 

setTimeout

 ,它計時結束後它的回調裡面引用的對象占用的記憶體是可以被回收的. 當然有些場景 

setTimeout

 的計時可能很長, 這樣的情況下也是需要納入考慮的.

chrome中檢視

老版本的在 

Timeline

 中檢視, 新版本的在 

performance

 中檢視:

javaScript 中的垃圾回收和記憶體洩漏 

步驟:

  1. 打開開發者工具 

    Performance

  2. 勾選 

    Screenshots

     和 

    memory

  3. 左上角小圓點開始錄制(record)
  4. 停止錄制

圖中 

Heap

 對應的部分就可以看到記憶體在周期性的回落也可以看到垃圾回收的周期,如果垃圾回收之後的最低值(我們稱為min),min在不斷上漲,那麼肯定是有較為嚴重的記憶體洩漏問題.

關于工具的使用暫時在這裡淺嘗辄止了,後面再深入的學習了開發者工具方方面面的使用再來和大家分享.

參考文檔:

  1. MDN文檔
  2. 推薦給大家的一個ppt

廣而告之

本文釋出于薄荷前端周刊,歡迎Watch & Star ★,轉載請注明出處。