指因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存。內存泄漏並不是指內存在物理上的消失,javascript
而是應用程序分配某段內存後,因爲設計錯誤,致使在釋放該段內存以前就失去了對該段內存的控制,從而形成了內存的浪費。html
JavaScript對未聲明變量的處理方式:在全局對象上建立該變量的引用(即全局對象上的屬性,不是變量,由於它能經過 delete
刪除)。若是在瀏覽器中,全局對象就是window對象。java
若是未聲明的變量緩存大量的數據,會致使這些數據只有在窗口關閉或從新刷新頁面時才能被釋放。這樣會形成意外的內存泄漏。web
function foo(arg) { bar = "this is a hidden global variable with a large of data"; }
等同於:瀏覽器
function foo(arg) { window.bar = "this is an explicit global variable with a large of data"; }
另外,經過this建立意外的全局變量:緩存
function foo() { this.variable = "potential accidental global"; } // 當在全局做用域中調用foo函數,此時this指向的是全局對象(window),而不是'undefined' foo();
在JavaScript文件中添加 'use strict'
,開啓嚴格模式,能夠有效地避免上述問題。性能優化
function foo(arg) { "use strict" // 在foo函數做用域內開啓嚴格模式 bar = "this is an explicit global variable with a large of data";// 報錯:由於bar尚未被聲明 }
若是須要在一個函數中使用全局變量,能夠像以下代碼所示,在window上明確聲明:閉包
function foo(arg) { window.bar = "this is a explicit global variable with a large of data"; }
這樣不只可讀性高,並且後期維護也方便ide
談到全局變量,須要注意那些用來臨時存儲大量數據的全局變量,確保在處理完這些數據後將其設置爲null或從新賦值。函數
全局變量也經常使用來作cache,通常cache都是爲了性能優化纔用到的,爲了性能,最好對cache的大小作個上限限制。
由於cache是不能被回收的,越高cache會致使越高的內存消耗。
二、console.log
console.log
:向web開發控制檯打印一條消息,經常使用來在開發時調試分析。有時在開發時,須要打印一些對象信息,但發佈時卻忘記去掉 console.log
語句,這可能形成內存泄露。
在傳遞給 console.log
的對象是不能被垃圾回收 ♻️,由於在代碼運行以後須要在開發工具能查看對象信息。因此最好不要在生產環境中 console.log
任何對象。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Leaker</title> </head> <body> <input type="button" value="click"> <script> !function () { function Leaker() { this.init(); }; Leaker.prototype = { init: function () { this.name = (Array(100000)).join('*'); console.log("Leaking an object %o: %o", (new Date()), this);// this對象不能被回收 }, destroy: function () { // do something.... } }; document.querySelector('input').addEventListener('click', function () { new Leaker(); }, false); }() </script> </body> </html>
這裏結合Chrome的Devtools–>Performance作一些分析,操做步驟以下:
開啓【Performance】項的記錄
執行一次CG,建立基準參考線
連續單擊【click】按鈕三次,新建三個Leaker對象
執行一次CG
中止記錄
去掉console.log("Leaking an object %o: %o",(newDate()),this);
語句。重複上述的操做步驟
從對比分析結果可知, console.log
打印的對象是不會被垃圾回收器回收的。所以最好不要在頁面中 console.log
任何大對象,這樣可能會影響頁面的總體性能,特別在生產環境中。
除了 console.log
外,另外還有 console.dir
、 console.error
、 console.warn
等都存在相似的問題,這些細節須要特別的關注。
三、closures(閉包)
當一個函數A返回一個內聯函數B,即便函數A執行完,函數B也能訪問函數A做用域內的變量,這就是一個閉包——————本質上閉包是將函數內部和外部鏈接起來的一座橋樑。
function foo(message) { function closure() { console.log(message) }; return closure; } // 使用 var bar = foo("hello closure!"); bar()// 返回 'hello closure!'
在函數foo內建立的函數closure對象是不能被回收掉的,由於它被全局變量bar引用,處於一直可訪問狀態。經過執行 bar()
能夠打印出 hello closure!
。若是想釋放掉能夠將 bar=null
便可。
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多。
在JavaScript中,DOM操做是很是耗時的。由於JavaScript/ECMAScript引擎獨立於渲染引擎,而DOM是位於渲染引擎,相互訪問須要消耗必定的資源。如Chrome瀏覽器中DOM位於WebCore,而JavaScript/ECMAScript位於V8中。
假如將JavaScript/ECMAScript、DOM分別想象成兩座孤島,兩島之間經過一座收費橋鏈接,過橋須要交納必定「過橋費」。JavaScript/ECMAScript每次訪問DOM時,都須要交納「過橋費」。
所以訪問DOM次數越多,費用越高,頁面性能就會受到很大影響。瞭解更多ℹ️
爲了減小DOM訪問次數,通常狀況下,當須要屢次訪問同一個DOM方法或屬性時,會將DOM引用緩存到一個局部變量中。
但若是在執行某些刪除、更新操做後,可能會忘記釋放掉代碼中對應的DOM引用,這樣會形成DOM內存泄露。
<script> var refA = document.getElementById('refA'); var refB = document.getElementById('refB'); document.body.removeChild(refA); // #refA不能GC回收,由於存在變量refA對它的引用。將其對#refA引用釋放,但仍是沒法回收#refA。 refA = null // 還存在變量refB對#refA的間接引用(refB引用了#refB,而#refB屬於#refA)。將變量refB對#refB的引用釋放,#refA就能夠被GC回收。 refB = null </script>
五、定時器
在JavaScript經常使用 setInterval()
來實現一些動畫效果。固然也可使用鏈式 setTimeout()
調用模式來實現:
setTimeout(function() { // do something. . . . setTimeout(arguments.callee, interval); }, interval);
若是在不須要 setInterval()
時,沒有經過 clearInterval()
方法移除,那麼 setInterval()
會不停地調用函數,直到調用 clearInterval()
或窗口關閉。
若是鏈式 setTimeout()
調用模式沒有給出終止邏輯,也會一直運行下去。所以再不須要重複定時器時,確保對定時器進行清除,避免佔用系統資源。
六、EventListener
作移動開發時,須要對不一樣設備尺寸作適配。如在開發組件時,有時須要考慮處理橫豎屏適配問題。通常作法,在橫豎屏發生變化時,須要將組件銷燬後再從新生成。
而在組件中會對其進行相關事件綁定,若是在銷燬組件時,沒有將組件的事件解綁,在橫豎屏發生變化時,就會不斷地對組件進行事件綁定。這樣會致使一些異常,甚至可能會致使頁面崩掉。
同一個元素節點註冊了多個相同的EventListener,那麼重複的實例會被拋棄。
這麼作不會讓得EventListener被重複調用,也不須要用removeEventListener手動清除多餘的EventListener,由於重複的都被自動拋棄了。
而這條規則只是針對於命名函數。對於匿名函數,瀏覽器會將其看作不一樣的EventListener,因此只要將匿名的EventListener,命名一下就能夠解決問題: