1. 程式人生 > >關於閉包和作用域的問題

關於閉包和作用域的問題

++ 這也 可見 函數 有一個 ole outside class ID

首先先引用《JavaScript權威指南》裏面的一句話來開始我的博客:函數的執行依賴於變量作用域,這個作用域是在函數定義時決定的,而不是函數調用時決定的。

因此,就出現了如下的幾串代碼:

var a="outside";
function area(){
    var a="inside";
    function b(){
        return a;
    }
    return b();
}
area();

結果為:inside

這樣的結果並不太出乎意料,因為在執行area()的時候,返回值就已經是b()了,也就是函數b已經是調用之後才被返回的,當然返回的是局部變量a。

那如果我返回的值不是執行函數b後的結果,而是函數b呢?

var a="outside";
function area(){
    var a="inside";
    function b(){
        return a;
    }
    return b;
}
area()();

結果還是inside

js函數的執行用到了作用域鏈,而作用域鏈是在函數定義的時候創建的,在上面兩個例子中,函數b()是定義在局部作用域裏面的,也就是說,它的返回值a早就註定是局部變量的a了,無論外面的area()函數的返回值是否為執行過的函數b()的結果。其實這個就是閉包,為什麽閉包能夠讓局部變量的值始終保持在內存中?《JavaScript權威指南》裏面有這樣一段話:每次調用函數,都會為之創建一個新的對象來保存局部變量,然後把該對象添加至作用域鏈中(每次調用就創建一個新的,調用多少次,創建多少個,執行結果互不影響)。當函數返回時,本來應該是直接從作用域鏈中將這個對象刪除,但是閉包的出現讓這一切變得不簡單。當返回的是一個嵌套函數的時候,就會有一個外部的引用指向這個嵌套的函數,可以理解為外部對它進行調用,或者賦值給某個變量,在js垃圾回收機制中,一旦某個變量不再被引用,那麽這個變量將會被回收。由此可見之前綁定在作用域鏈上的對象由於閉包的關系不會被當做垃圾回收,這也就是閉包能夠讓局部變量的值始終保持在內存中的原因。

接下來我們來看一下幾段有關於閉包和作用域的代碼,這幾段代碼都采用自權威指南。

function add(){
    var num=0;
    return {
        count:function(){return num++;},
        reset:function(){num=0;}
    };
}
var a=add(),b=add(); //創建兩個計時器
a.count(); //0       第一行
b.count(); //0       第二行
a.reset(); //重置    第三行
a.count(); //0       第四行
b.count(); //1       第五行

這段代碼的結果正好印證了每次調用就會創建不同的對象,然後保存在作用域鏈上。第一行和第二行是兩個計時器對計時函數的調用,很明顯他們互不影響,第三行a計時器進行重置,當然對b計時器無效了,互不影響的嘛!

下面這兩段代碼是我們經常碰到的筆試題

function a(v){
    return function(){return v;};
}
var funs=[];
for(var i=0;i<10;i++){
    funs[i]=a(i);
}
console.log(funs[5]()); //5
function a(){
    var funs=[];
    for(var i=0;i<10;i++){
        funs[i]=function(){
            return i;
        }
    }
    return funs;
}
var funs=a();
console.log(funs[5]()); //10 

第一段代碼執行結果為5,第二段為10。原因可以根據前面的篇幅來解釋。

第一段代碼只有一個閉包,但是有10個外部調用函數,也就是10個對象保存在作用域鏈上,執行結果互不幹擾,所以當調用funs[5]() 的時候,結果肯定也是5。

第二段代碼有十個閉包,共享同一個外部函數的局部變量,外部調用函數只有一個,當9次循環執行完之後,i還被++了,所以結果是10,所以外部函數調用到內嵌函數的時候,結果為10。

關於閉包和作用域的問題