Javascript的做用域、做用域鏈以及閉包

1、javascript中的做用域javascript

   ①全局變量-函數體外部進行聲明html

   ②局部變量-函數體內部進行聲明java

1)函數級做用域數組

javascript語言中局部變量不一樣於C#Java等高級語言,在這些高級語言內部,採用的塊級做用域中會聲明新的變量,這些變量不會影響到外部做用域。瀏覽器

javascript則採用的是函數級做用域,也就是說js建立做用域的單位是函數。閉包

例如:函數

C#當中咱們寫以下代碼:spa

static void Main(string[] args)
{
     for (var x = 1; x < 10; x++)
     {
           Console.WriteLine(x.ToString());
     }
     Console.WriteLine(x.ToString());
}

上面代碼會出現以下的編譯錯誤:code

The name 'x' does not exist in the current context        htm

一樣在javascript當中寫以下代碼:

<script>
    function main() {
        for (var x = 1; x < 10; x++) {
             console.log(x.toString());
         }
         console.log(x.toString());
     }
     main();
</script>

輸出結果以下:

[Web瀏覽器] "1"        

[Web瀏覽器] "2"        

[Web瀏覽器] "3"        

[Web瀏覽器] "4"        

[Web瀏覽器] "5"        

[Web瀏覽器] "6"        

[Web瀏覽器] "7"        

[Web瀏覽器] "8"        

[Web瀏覽器] "9"        

[Web瀏覽器] "10"        

這就說明了,「塊級做用域」和「函數級做用域」的區別,塊級做用域當離開做用域後,外部就不能用了,就像上面的C#例子,"x"離開for循環後就不能用了,可是javascript中不同,它還能夠進行訪問。

 

 2)變量提高

再看javascript中做用域的一個特性,例子以下:

function func(){
    console.log(x);
    var x = 1;
    console.log(x);
}        
func();

輸出結果:

[Web瀏覽器] "undefined"        

[Web瀏覽器] "1"                 

 

如上面的結果:有意思的是,爲何第一個輸出是「undefined」呢?這就涉及到javascript中的「變量提高」,其實我感受叫「聲明提高」更好,由於這個機制就是把變量的聲明提早到函數的前面。並不會把值也一樣提高,也就是爲何第一次沒有輸出「1」的緣由。

上面的代碼就至關於:

function func(){
    var x;
    console.log(x);
    var x = 1;
    console.log(x);
}        
func();

3)函數內部用不用「var」對程序的影響

這是個值得注意的地方:

var x = 1;
function addVar(){
    var x = 2;
    console.log(x);            
}
addVar();
console.log(x);

輸出:

[Web瀏覽器] "2"        

[Web瀏覽器] "1"        

當在函數內部去掉var以後,再執行:

 var x = 1;
 function delVar(){
       x = 2;
       console.log(x);            
  }
 delVar();
 console.log(x);

[Web瀏覽器] "2"        

[Web瀏覽器] "2"        

上面的例子說明了,在函數內部若是在聲明變量沒有使用var,那麼聲明的變量就是全局變量。

 

2、javascript的做用域鏈

先看以下的代碼:

 

var name="Global";
function t(){
        var name="t_Local";
        
        function s1(){
            var name="s1_Local";
                console.log(name);
        }
        function s2(){
                console.log(name);
        }
        s1();
        s2(); 
}
t(); 

輸出結果:

[Web瀏覽器] "s1_Local"

[Web瀏覽器] "t_Local"

 

那麼就有幾個問題:

1.爲何第二次輸出的不是s1_Local?

2.爲何不是Global

解決這個兩個問題就在於做用域鏈

下面就解析一下這個過程,

 

例如當上面程序建立完成的時候,會造成上圖中的鏈狀結構(假想的),因此每次調用s1()函數的時候,console.log(name);先會在他本身的做用域中尋找name這個變量,這裏它找到name=s1_Local」,因此就輸出了s1_Local,而每次調用s2()的時候,它和s1()函數過程同樣,只不過在自身的做用域沒有找到,因此向上層查找,在t()函數中找到了,因而就輸出了"t_Local"

一樣若是咱們能夠驗證一下,若是把t中的name刪除,能夠看看輸出是否是「Global

結果以下:

[Web瀏覽器] "s1_Local"

[Web瀏覽器] "Global"        

固然具體每個做用域直接是如何鏈接的,請參考

http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

其中也說明了爲何JS當中應該儘可能減小with關鍵字的使用。

 

3、閉包

瞭解了做用域和做用域鏈的概念以後,再去理解閉包就相對容易了。

1.閉包第一個做用

仍是先看例子:

function s1() {
    var x = 123;
    return s2();
}

function s2() {
    return x;
}

alert(s1());

這裏我想彈出x的值,可是卻發生了錯誤 "Uncaught ReferenceError: x is not defined",根據做用域鏈的知識,瞭解到由於s2中沒有x的定義,而且向上找全局也沒有x定義,因此就報錯了。也就是說s1s2不可以共享x的值。

那麼問題來了,我想要訪問s1當中的x,怎麼弄?

修改代碼以下:

function s1() {
    var x = 123;
    return function(){
        return x;
    };
}
    
var test = s1();
console.log(test());

結果爲:

[Web瀏覽器] "123"        

解釋:由於function本質也是數據,因此它和x的做用域相同,能夠訪問x。這樣就至關於對外部開放了一個能夠訪問內部變量的接口。

還能夠怎麼玩,稍微修改一下代碼

var func;
function f(){
var x='123';
func=function(){
    return x;
    };
}
f();
alert(func());

定義一個全局的變量,而後在函數內部讓其等於一個函數,這樣就能夠經過這個全局變量來進行訪問x了。

綜上:閉包是啥?閉包就至關於函數外部和內部的橋樑,經過閉包能夠在外部訪問函數內部的變量。這也是閉包的第一個做用。

 

2.閉包的第二個做用

先看代碼:

function f1(){
    var n=1;
    add=function(){
        n+=1;
    };
    function f2(){
        console.log(n);
        return '輸出完成';
    }
    return f2;
}
var res=f1();
console.log(res());
add();
console.log(res());

輸出結果:

[Web瀏覽器] "1"

[Web瀏覽器] "輸出完成"

[Web瀏覽器] "2"

[Web瀏覽器] "輸出完成"

 

問題爲何第二次輸出的結果n變成了2,沒有被清除?

個人理解:res是一個全局變量,一直駐留在內存當中,它就至關於f2函數,f2函數又要依賴於f1函數,因此f1也駐留在內存當中,保證不被GC所回收,每一次調用add函數的時候,就至關於把f1中的n從新賦值了,這樣n的值就變化了。這也是閉包的第二個做用,可讓變量的值始終保存在內存當中

 

3.閉包的應用

①能夠作訪問控制(至關於C#當中的屬性)

//定義兩個變量,用於存儲取值和存值
var get,set; 
//定義一個自調用函數,設定set和get方法
(function(){
    //設定x的默認值
    var x = 0;
    set = function(n){
        x = n;
    }
    get = function(){
        return x;
    }
})();

console.log(get());
set(5);
console.log(get());

輸出結果:

[Web瀏覽器] "0"

[Web瀏覽器] "5"

 

②能夠用作迭代器

//定義一個函數,裏面使用了閉包
function foo(myArray){
    var i=0;
    //閉包迭代器
    next=function(){
        //每次返回數組的當前值,下標+1
        return myArray[i++];
    }
}
//調用foo,參數爲一個數組
foo(['a','b','c','d']);
//每次調用next都會打印數組的一個值
console.log(next());
console.log(next());
console.log(next());
console.log(next());

輸出結果:

[Web瀏覽器] "a"

[Web瀏覽器] "b"

[Web瀏覽器] "c"

[Web瀏覽器] "d"

 

③閉包在循環中的使用

例1
function f(){
    var a=[];
    var i;
    for(i=0;i<3;i++){
        a[i]=function(){
            return i;
        };
    }
    return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());

輸出結果:

[Web瀏覽器] "3"

[Web瀏覽器] "3"

[Web瀏覽器] "3"

爲何結果不是012

這裏咱們使用了閉包,每次循環都指向了同一個局部變量i,可是閉包不會記錄每一次循環的值,只保存了變量的引用地址,因此當咱們在調用test[0]()test[1]()test[2]()的時候都返回的是for循環最後的值,也就是3的時候跳出了循環。

 

2:我想輸出0,1,2怎麼搞?

function f(){
    var a=[];
    var i;
    for(i=0;i<3;i++){
        a[i]=(function(x){
            return function(){
                return x;
            }
        })(i);
    }
    return a;
}
var test=f();
console.log(test[0]());
console.log(test[1]());
console.log(test[2]());

結果:

[Web瀏覽器] "0"

[Web瀏覽器] "1"

[Web瀏覽器] "2"

關於這個爲何和上面不同,我在知乎上找到了一篇文章,總結的很是好,

★★★★★引用自:http://www.zhihu.com/question/33468703

相關文章
相關標籤/搜索