作為程式員(更高大尚的稱謂:研軟體研發)的我們,無論是用Javascript,還是.net, java語言,肯定都遇到過記憶體泄漏的問題。只不過他們都有GC機制來幫助程式員完成記憶體回收的事情,如果你是C++開發者(你懂的)。。。。。,如果你是前端開發者,肯定在使用Javascript(你或者會說,Js ...
作為程式員(更高大尚的稱謂:研軟體研發)的我們,無論是用Javascript,還是.net, java語言,肯定都遇到過記憶體泄漏的問題。只不過他們都有GC機制來幫助程式員完成記憶體回收的事情,如果你是C++開發者(你懂的)。。。。。,如果你是前端開發者,肯定在使用Javascript(你或者會說,Js是世界上最棒的語言),但我這裡也得告訴你,Js的記憶體泄漏會來得更為突然,或者讓你都無法察覺。本文就帶大家領略一下Js的風騷:
一、模塊化引起的記憶體泄漏
代碼如下:
// module date.js let date = null; export default { init () { date = new Date(); } } // main.js import date from 'date.js'; date.init();
上述是我們在現代前端工程方案中常見的代碼格式,寫一個模塊,然後導出這一個模塊。這裡你應該知道date.js中的date是靜態的(也就是你在N處導入date.js這個模塊),但他們的date這個變數是共用的,一處改變,其他地方也對應發生變化。
二、假OOP範式引起的記憶體泄漏
在這裡我為什麼叫他假OOP呢,原因是這代碼是想實現OOP範式卻讓自己掉到坑裡去了,先上代碼:
var fun = function(arg){ this.sarg = arg; var self = this; return function(){ console.log(self.sarg); } } var fn = new fun('data arg'); fn();
首先,定義fun這個函數變數,然後返回一個function(這可以說就是典型的閉包)。閉包函數內引用外面的this對象(var self = this)。
然後,通過new的方式調用fun,返回值用fn接受,這裡誰都知道返回的是一個函數,所以可以括弧運算符進行執行。
2.1 利用chrome的memory面板進行分析
定位到memory面板,然後刷新頁面,再單擊下圖中所示的 'collect garbage'圖標(也就是像回收站的圖標),強制進行一次gc的回收,這樣可以確保我們分析的對象就是可以存在記憶體泄漏的對象(至少他們是gc不可回收的對象)。
此圖是上述代碼片段在chrome瀏覽器中執行完成後,不能被gc回收的記憶體變數。
2.2 我認為的原因
先貼出發生記憶體泄漏的代碼
var fun = function(arg){ this.sarg = arg; //記憶體泄漏 var self = this;//記憶體泄漏 return function(){ console.log(self.sarg);//記憶體泄漏 } } var fn = new fun('data arg'); //記憶體泄漏 fn();
我認為的原因有以下幾點:
1. 使用new運算符,他會創建一個對象,然後執行構造函數,並將構造函數對應的prototype(也就是原型)複製到新的對象上。
2. 上述new出來的新對象,在執行構造函數時,其this就指向了這個new出來的新對象
3. 然後上述代碼在構造函數中又返回了一個函數,且函數中引用了new出來的新對象,返回函數賦值給了fn變數
4. 最的執行fn變數,正確輸出我們想要的內容,這樣程式就跑了(可以,我們new出來的新對象,沒有人管也了,所以他就泄漏了)。
2.3 總結:
因為正常情況下,我們對一個function進行new操作的時候,在構造函數內是不會進行返回的,其實這個時候new操作預設給你返回的就是構造函數中的this對象。上述代碼不建議出現在項目代碼中,這是典型的錯誤寫法,並示例只是為了演示泄漏。
三、DOM事件引起的記憶體泄漏
如果你是Jquery的忠粉,這部分可能對你有幫助,先上代碼:
//html: <input type="file" id="file" /> <button type="button" onclick="remove()" >but</button> //js: var file = document.getElementById("file"); file.addEventListener('change',function(event){ console.log(event.target.value); }); function remove(){ file.remove(); }
首先我們在html中寫兩個標簽,一個是file、一個是button;然後在js中對file標簽綁定了change事件,然後對button綁定一個remove方法,用於移除file標簽。
3.1 記憶體泄漏分析
在我們執行了remove方法後,然後收集記憶體分析:
我們還按照示例二相同的操作,打開memory面板,然後執行一次GC回收後收集記憶體數據,然後查看Detached Dom tree(這就表示與DOM樹失去聯繫的對象),然後我們把滑鼠移動到native上,就會顯示記憶體泄漏的代碼位置。
Jquery忠粉們可以註意了,無論你是用的bind還是on進行事件的綁定,如果你在移除這些DOM元素前,沒有進行相應的unbind或是off操作,那麼恭喜你,記憶體一定泄漏了。