1、意外的全局變量
未定義的變量會在全局對象創建一個新變量,如下。
function foo(arg) {
bar = "this is a hidden global variable";
}
函數 foo
內部忘記使用 var
,實際上JS會把bar掛載到全局對象上,意外創建一個全局變量。
function foo(arg) {
window.bar = "this is an explicit global variable";
}
另一個意外的全局變量可能由 this
創建。
function foo() {
this.variable = "potential accidental global";
}
// Foo 調用自己,this 指向了全局對象(window)
// 而不是 undefined
foo();
解決方法:
在 JavaScript 文件頭部加上 'use strict'
,使用嚴格模式避免意外的全局變量,此時上例中的this指向undefined
。如果必須使用全局變量存儲大量數據時,確保用完以後把它設置爲 null 或者重新定義。
被遺忘的計時器或回調函數
計時器setInterval
代碼很常見
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 處理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
上面的例子表明,在節點node或者數據不再需要時,定時器依舊指向這些數據。所以哪怕當node節點被移除後,interval 仍舊存活並且垃圾回收器沒辦法回收,它的依賴也沒辦法被回收,除非終止定時器。
var element = document.getElementById('button');
function onClick(event) {
element.innerHTML = 'text';
}
element.addEventListener('click', onClick);
對於上面觀察者的例子,一旦它們不再需要(或者關聯的對象變成不可達),明確地移除它們非常重要。老的 IE 6 是無法處理循環引用的。因爲老版本的 IE 是無法檢測 DOM 節點與 JavaScript 代碼之間的循環引用,會導致內存泄漏。
但是,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法(標記清除),已經可以正確檢測和處理循環引用了。即回收節點內存時,不必非要調用 removeEventListener
了
3、脫離 DOM 的引用
如果把DOM 存成字典(JSON 鍵值對)或者數組,此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。那麼將來需要把兩個引用都清除。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// 更多邏輯
}
function removeButton() {
// 按鈕是 body 的後代元素
document.body.removeChild(document.getElementById('button'));
// 此時,仍舊存在一個全局的 #button 的引用
// elements 字典。button 元素仍舊在內存中,不能被 GC 回收。
}
如果代碼中保存了表格某一個 <td>
的引用。將來決定刪除整個表格的時候,直覺認爲 GC 會回收除了已保存的 <td>
以外的其它節點。實際情況並非如此:此 <td>
是表格的子節點,子元素與父元素是引用關係。由於代碼保留了 <td>
的引用,導致整個表格仍待在內存中。所以保存 DOM 元素引用的時候,要小心謹慎。
4、閉包
閉包的關鍵是匿名函數可以訪問父級作用域的變量。
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
每次調用 replaceThing
,theThing
得到一個包含一個大數組和一個新閉包(someMethod
)的新對象。同時,變量 unused
是一個引用 originalThing
的閉包(先前的 replaceThing
又調用了 theThing
)。someMethod
可以通過 theThing
使用,someMethod
與 unused
分享閉包作用域,儘管 unused
從未使用,它引用的 originalThing
迫使它保留在內存中(防止被回收)。
解決方法:
在 replaceThing
的最後添加 originalThing = null
。