JavaScript中的內存釋放
一、如何查找上級作用域
在JavaScript中的預解析 ,有講到作用域鏈的概念,本次在正式講JavaScript中的內存釋放之前,先看一個問題: 如何查找當前作用域的上級作用域 。
var num = 20;
function fn() {
var num = 200;
return function () {
console.log(num);
};
}
var f = fn();
f(); // 輸出 200
以上代碼fn中返回了一個函數,用f去接收這個返回的函數,然后再執行f(),最后輸出的是200,剛接觸的同學可能會有疑問,為什么在全局作用域下執行的f(),為什么輸出的num不是全局作用域中的20,而是fn函數的私有作用域中的200!
上級作用域查找規則:看當前函數是在哪個作用域下定義的,那么它的上級作用域就是誰,和函數哪里執行沒有任何的關系。
以上的代碼在全局的變量和函數進行預解析之后,執行fn函數,fn函數又進行預解析形成自己的私有作用域,然后執行fn函數中的代碼,最后返回一個函數,該函數被f接收。當f執行的時候,有一行代碼輸出num: console.log(num) ,根據作用域搜索規則,首先在自己的作用域中找,沒有找到,然后再到上級的作用域中查找,根據作用域查找的規則, 只看當前函數在哪個作用域下定義的 ,所以f函數的上級作用域是fn。
如何查找上級作用域.png
有了以上的基礎之后,再看:
var num = 20;
function fn() {
var num = 200;
return function () {
console.log(num);
};
}
var f = fn();
f(); // 輸出 200
~function () {
var num = 2000;
f(); // 輸出什么呢?
}();</code></pre>
加上了一個自執行函數,我們知道自執行函數是有自己的作用域的,但是此時f函數執行,依然輸出200。 要時刻上級作用域的查找規則: 只看當前函數在哪個作用域下定義的 。
這時候很多同學可能會疑惑了,我們不是講解JavaScript的內存釋放的嘛?怎么還講起了作用域的內容,稍安勿躁...在JavaScript的內存釋放中要用到這些知識呢!
二、堆內存的釋放
對象數據類型或者函數類型在定義的時候,首先都會開辟一個堆內存,堆內存有一個引用地址,如果外面有引用這個地址,我們就說這個內存被占用了,就不能銷毀了。
var obj1 = {name:"iceamn"};
var obj2 = obj1;

堆內存.png
如果想要讓堆內存釋放(銷毀),只需要把所有引用它的變量賦值為null即可,如果當前的堆內存沒有任何東西被占用了,那么瀏覽器會在空閑的時候把它銷毀。也就是說,在上面的那種感覺情況下,只有把obj1和同obj2都置為null之后,0xff11這塊對堆內存才會被釋放,只要還有變量引用0xff11這塊內存,它就不會釋放。
三、棧內存的釋放
3.1、全局作用域
在全局作用域下,只有當頁面關閉的時候,全局作用域才會被銷毀。
3.2、私有作用域
一般情況下,函數執行會形成一個新的私有作用域(在ES6之前只有函數執行才會產生私有作用域),當私有作用域中的代碼執行完成后,當前作用域都會主動的進行釋放和銷毀。
不過依然有特殊的情況存在:當前私有作用域中的部分內容被作用域以外的東西占用了,那么當前作用域就不能銷毀了。
3.2.1、 函數返回來一個引用數據類型的值(數組、函數...),并且該引用類型的值在函數的外面被一個其他變量接收了,這種情況下形成的私有作用域都不會銷毀。
注意兩個條件:
(1)函數返回引用數據類型的值;
(2)該引用類型的值在函數外面被一個其他變量接收了;
function fn() {
var num = 100;
return function () {
num ++;
console.log(num);
}
}
var f = fn(); // fn執行形成的作用域就不能再銷毀了
注意:即使fn返回的函數中什么代碼都沒有,沒有使用到fn私有作用域中的任何變量和函數,在以上情況下,fn的私有作用域也不會被銷毀,即:
function fn() {
var num = 100;
return function () {
}
}
var f = fn();
3.2.2、 在一個私有作用域中,給DOM元素綁定方法,私有作用域不能被銷毀
var btn = document.getElementById('btn1');
~function () {
btn.onclick = function () {
}
}();
在自執行函數中形成了一個私有的作用域,在這個私有作用域中為頁面上的一個button元素綁定了點擊事件,所以這個私有作用域也不能被銷毀。

自執行函數的情況.png
3.2.3、 “不立即銷毀”
function fn() {
var num = 100;
return function () {
}
}
fn()(); // 首先執行fn,返回一個小函數對應的內存地址,然后緊接著讓返回的小函數再執行
以上代碼就是“不立即銷毀”的情況,fn返回的函數沒有被其他的任何變量占用,但是還需要執行一次,所以暫時不能銷毀,但返回的值執行完成后,瀏覽器會在空閑的時候把它銷毀了。
還記得一開始介紹的上級作用域嗎,我們再對那張圖進行分析:

上級作用域的情況.png
只要某作用域還有被引用,那么該作用域就不能被銷毀,一旦沒有任何變量引用了,該私有作用域就會被銷毀了。
四、練習題
-
第一題
function fn() {
var i = 10;
return function (n) {
console.log(n + (++i));
};
}
var f = fn();
f(10); // 21
f(20); // 32
fn()(10); // 21
fn()(20); // 31
-
第二題
function fn(i) {
return function (n) {
console.log(n + i++);
}
}
var f = fn(13);
f(12);//->25
f(14);//->28
fn(15)(12);//->27
fn(16)(13);//->29
來自:http://www.jianshu.com/p/3b7946c4b118