天天看點

javascript的作用域鍊及閉包

作用域

作用域就是變量和函數的可通路範圍。JavaScript沒有塊級作用域,隻有函數作用域:變量在聲明他們的函數體及其子函數内是可見的。

變量沒有在函數内聲明或沒有帶var就是全局變量,擁有全局作用域,window對象的所有屬性擁有全局作用域,在代碼任何地方都可以通路。

函數内部聲明并且以var修飾的變量就是局部變量,隻能在函數體内使用,函數的參數雖然沒有使用var單仍然是局部變量。

var a=; //a是全局變量
function fun(b){ //b是局部變量
    c=; //c是全局變量
    var d=; //d是局部變量
    function subFn(){
        var e=d; //父函數的局部變量對子函數可見
        for(var i=;i<;i++){
           alert(i);
        }
        alert(i);//3: 在for循環内聲明,循環外function内仍然可見,沒有塊作用域
    }
}
alert(c); //2:在function内聲明但不帶var修飾,仍然是全局變量
           
真正解釋執行之前,JavaScript解釋器會預解析代碼,将變量、函數聲明部分提前解釋
alert(a); //undefined
var a=;
alert(a); //3
alert(b); //不會有任何輸出
           
上面代碼在執行var a =3;前聲明部分就已經預解析(但是不會執行指派語句)。是以上面代碼和下面的代碼是一樣的。
var a;
alert(a); //undefined
a=;
alert(a); //3
alert(b); //不會有任何輸出
           

執行環境

執行環境(execution econtext)是javascript中最為重要的一個概念。執行環境定義了變量或函數有權通路的其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變量對象(Variable Object,VO),環境中定義的所有變量和函數都儲存在這個對象中。雖然我們編寫的代碼無法通路這個對象,但解析器在處理資料時會在背景使用它。

全局執行環境是最外圍的一個執行環境。在web浏覽器中全局執行環境是window對象,是以所有全局變量和函數都是作為window對象的屬性和方法建立的(全局執行環境直到應用程式退出–例如關閉網頁或浏覽器時才會被銷毀)。

作用域鍊

當代碼在一個環境中執行時,會建立變量對象的一個作用域鍊(scope chain,sc),來保證對執行環境有權通路的變量和函數的有序通路。作用域的第一個對象始終是目前執行代碼所在環境的變量對象(VO)
function a(x,y){
    var b=x+y;
    return b;
}
           

在函數a建立的時候它的作用域鍊填入全局對象,全局對象中有所有全局變量。

javascript的作用域鍊及閉包
如果執行環境是函數,那麼将其活動對象(activation object, AO)作為作用域鍊第一個對象,第二個對象是包含環境,下一個是包含環境的包含環境。。。。。
function a(x,y){
    var b=x+y;
    return b;
}
var tatal=a(,);
           

這時候 var total=a(5,10);語句的作用域鍊如下

javascript的作用域鍊及閉包
在函數運作過程中辨別符的解析是沿着作用域鍊一級一級搜尋的過程,從第一個對象開始,逐級向後回溯,直到找到同名辨別符為止,找到後不再繼續周遊,找不到就報錯。

閉包

背景

由于種種原因我們需要讀取函數内的局部變量。正常情況下是辦不到,但是可以在函數内部,在定義一個函數。
function a(){
    var n=;
  function b(){
       alert(++n);
  }
    return b;
}
var res=a();
b(); //1
           

在上面的代碼中,函數b就被包括在函數a内部,這時a内部的所有局部變量,對b都是可見的。

既然b可以讀取a中的局部變量,那麼隻要把b作為傳回值,我們不就可以在a外部讀取它的内部變量了!

閉包的概念

上一節代碼中的b函數,就是閉包。閉包就是能夠讀取其他函數内部變量的函數。由于在Javascript語言中,隻有函數内部的子函數才能讀取局部變量,是以可以把閉包簡單了解成”定義在一個函數内部的函數”。

是以,在本質上,閉包就是将函數内部和函數外部連接配接起來的一座橋梁。

閉包的用途

  • 使a函數的局部變量的值始終保持在記憶體中。使javascript的垃圾回收機制GC不會收回a所占用的資源。
  • 讀取函數内部的變量。

閉包的應用場景

保護函數内的變量安全。函數a中的n隻有函數b可以通路,無法通過其他途徑通路,是以保護了n的安全性。

- 在記憶體中維持一個變量。函數a的n一直存在記憶體中,是以每次執行c(),都會給i自加1。

- 通過保護變量的安全實作JS私有屬性和私有方法(不能被外部通路)

function Constructor(...) {  
  var that = this;  
  var membername = value; 
  function membername(...) {...}
}
           

使用閉包的注意點

  • 由于閉包會使得函數中的變量都被儲存在記憶體中,記憶體消耗很大,是以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導緻記憶體洩露。解決方法是,在退出函數之前,将不使用的局部變量全部删除。
  • 閉包會在父函數外部,改變父函數内部變量的值。是以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把内部變量當作它的私有屬性(private value),這時一定要小心,不要随便改變父函數内部變量的值。

參考文獻

  1. JavaScript作用域鍊
  2. 學習Javascript閉包(Closure)

繼續閱讀