通過作用域鏈去理解閉包

作用域鏈:當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,作用域鏈的作用是保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈包含了執行環境棧中的每個執行環境對應的變量對象,通過作用域鏈,可以決定變量的訪問和標識符的解析(即變量名或者函數名的搜索)。

當訪問一個變量或者調用一個函數時,JS引擎將不同的執行環境中的變量對象按規則構建一個鏈表,在訪問一個變量時,先在鏈表的第一個變量對象上面進行查找,如果沒有找到,那麼繼續在第二個變量對象上找,直到搜索到全局執行環境的變量對象,即window對象。

例:

     var a=”test”;

     function f1(){

        var add=”test1”;

        function f2(){

var b=add;

add=a;

a=b;

// 這裏可以訪問a、add以及f2;

}

// 這裏可以訪問a、f2,但是不能訪問b!

 test();

}// 這裏只能訪問a!

f1();

在這個例子當中,一共有3個執行環境:全局執行環境、f1()局部執行環境、f2()局部執行環境。

1. 全局環境有一個變量a和一個函數f1();

2. f1()函數的局部環境有一個add屬性和一個f2()函數,此時f1函數可以訪問自身以及它的外圍(即全局環境)中的變量;

3. f2()函數的局部環境有一個變量b,在該函數內部可以訪問上面兩個環境,即f1()和全局環境,因爲這兩個環境是它的父級環境。;

上面例子的作用域鏈可以用一張圖來表示:

 

閉包:所謂的閉包,用一句話來概括,那就是可以讀取其他函數內部的變量的函數。從這裏我們可以知道,閉包是一個函數,常見創建閉包的方式是在一個函數內部創建另一個函數,即嵌套。

舉個簡單點的例子:

function f1(){

  var a=1;

}alert(a);//error.

運行結果顯示錯誤,即在函數外部是訪問不了函數內部的變量的,這也印證前面的結論。但是有時候我們需要從外部讀取函數內部的變量,那又該怎麼辦呢?

function f1(){

  var a=1;

function f2(){

   alert(a);

}

return f2;

}

var result=f1();

result();//1

如果已經理解了前面作用域鏈的原理的話,那麼這個例子的運行結果就比較好理解了,因爲前面作用域鏈的例子當中也近似地用到了閉包。由作用域鏈的原理我們知道,函數可以訪問父函數內部的所有局部變量,但反過來就不行,所以要想從外部讀取函數內部的變量,我們可以變通一下,我們可以在這個函數內部再創建一個函數,然後把這個函數作爲返回值,這樣就可以讀取函數內部的變量了!

閉包的作用:一個就是前面的可以讀取其他函數內部的變量;另外一個就是讓這些變量的值始終保存在內存中:

例:

function f1(){

  var a=1;

sum=function(){a+=1;}

function f2(){

   alert(a);

}

return f2;

}

var result=f1();

result();//1

sum();

result();//2

在這個例子當中,result一個運行了兩次,第一次值爲1,第二次值爲2,這說明了f1中的局部變量a一直保存在內存當中,並沒有在f1調用之後被自動清除。這是爲什麼呢?這是因爲f1是f2的父函數,而f2被賦予給了一個全局變量sum,這導致了f2始終在內存中,而f2是依賴於f1的,因此f1也始終在內存當中,不會再被調用之後被垃圾回收機制回收。

另外在本例當中,sum由於沒有進行var聲明,所以它是一個全局變量,除此之外它還是一個匿名函數,而且本身也是一個閉包。

閉包的缺點:內存泄露

內存泄露是指一塊內存既不能被使用,也不能被回收,從而影響性能,甚至導致程序崩潰。由前面例子我們知道,由於閉包會使得變量一直存在在內存當中,這樣的會使得內存消耗很大,從而影響網頁的性能,所以不能濫用閉包。那應該然後解決由閉包引起的內存泄露呢?解決的方法是,在代碼運行完之時,將不使用的形成循環引用的局部變量刪除(例如設置爲null)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章