更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看前端
------ 如下是正文 ------node
本期的主題是調用堆棧,本計劃一共28期,每期重點攻克一個面試重難點,若是你還不瞭解本進階計劃,文末點擊查看所有文章。webpack
若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。git
上篇文章詳細介紹了內存回收和內存泄漏,今天咱們繼續這個篇幅,不太重點是內存泄漏可能發生的緣由。es6
經常使用垃圾回收算法叫作**標記清除 (Mark-and-sweep) **,算法由如下幾步組成:github
一、垃圾回收器建立了一個「roots」列表。roots 一般是代碼中全局變量的引用。JavaScript 中,「window」 對象是一個全局變量,被看成 root 。window 對象老是存在,所以垃圾回收器能夠檢查它和它的全部子對象是否存在(即不是垃圾);web
二、全部的 roots 被檢查和標記爲激活(即不是垃圾)。全部的子對象也被遞歸地檢查。從 root 開始的全部對象若是是可達的,它就不被看成垃圾。面試
三、全部未被標記的內存會被當作垃圾,收集器如今能夠釋放內存,歸還給操做系統了。算法
現代的垃圾回收器改良了算法,可是本質是相同的:可達內存被標記,其他的被看成垃圾回收。跨域
劃重點 這是個考點
未定義的變量會在全局對象建立一個新變量,以下。
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
了。
若是把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 元素引用的時候,要當心謹慎。
閉包的關鍵是匿名函數能夠訪問父級做用域的變量。
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
。
PS:今晚弄到很晚,因爲時間問題,就再也不詳細介紹Chrome 內存剖析工具,有興趣的你們去原文查看。
週末彙總將在週日早上發送,週六會發送其餘類型的文章,敬請期待。
問題一:
從內存來看 null 和 undefined 本質的區別是什麼?
解答:
給一個全局變量賦值爲null,至關於將這個變量的指針對象以及值清空,若是是給對象的屬性 賦值爲null,或者局部變量賦值爲null,至關於給這個屬性分配了一塊空的內存,而後值爲null, JS會回收全局變量爲null的對象。
給一個全局變量賦值爲undefined,至關於將這個對象的值清空,可是這個對象依舊存在,若是是給對象的屬性賦值 爲undefined,說明這個值爲空值
擴展下:
聲明瞭一個變量,但未對其初始化時,這個變量的值就是undefined,它是 JavaScript 基本類型 之一。
var data;
console.log(data === undefined); //true
複製代碼
對於還沒有聲明過的變量,只能執行一項操做,即便用typeof操做符檢測其數據類型,使用其餘的操做都會報錯。
//data變量未定義
console.log(typeof data); // "undefined"
console.log(data === undefined); //報錯
複製代碼
值 null
特指對象的值未設置,它是 JavaScript 基本類型 之一。
值 null
是一個字面量,它不像undefined
是全局對象的一個屬性。null
是表示缺乏的標識,指示變量未指向任何對象。
// foo不存在,它歷來沒有被定義過或者是初始化過:
foo;
"ReferenceError: foo is not defined"
// foo如今已是知存在的,可是它沒有類型或者是值:
var foo = null;
console.log(foo); // null
複製代碼
問題二:
ES6語法中的 const 聲明一個只讀的常量,那爲何下面能夠修改const的值?
const foo = {};
// 爲 foo 添加一個屬性,能夠成功
foo.prop = 123;
foo.prop // 123
// 將 foo 指向另外一個對象,就會報錯
foo = {}; // TypeError: "foo" is read-only
複製代碼
解答:
const
實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const
只能保證這個指針是固定的(即老是指向另外一個固定的地址),至於它指向的數據結構是否是可變的,就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。
<script>
console.log(fun)
console.log(person)
</script>
<script>
console.log(person)
console.log(fun)
var person = "Eric";
console.log(person)
function fun() {
console.log(person)
var person = "Tom";
console.log(person)
}
fun()
console.log(person)
</script>
複製代碼
上面代碼的執行結果是什麼?先本身分析,而後再到瀏覽器中執行。
進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,歡迎領取,以爲不錯點個star。
我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!