javascript閉包(Closure)初探

來源:互聯網
上載者:User

closure被翻譯成“閉包”,感覺這東西被封裝的太學術化。下面參考書本和網上資源簡單探討一下(理解不當之處務請留意)。
1、什麼是閉包
官方的回答:所謂“閉包”,指的是一個擁有許多變數和綁定了這些變數的環境的運算式(通常是一個函數),因而這些變數也是該運算式的一部分。
看了上面的定義,如果你不是高手,我堅信你會和我一樣憤怒的質問:這tmd是人話嗎?
要理解閉包,還是代碼最有說服力啊,上代碼:

Code
function funcTest()
{
  var tmpNum=100; //私人變數

  //在函數funcTest內定義另外的函數作為funcTest的方法函數
  function innerFuncTest(
  {
       alert(tmpNum); //引用外層函數funcTest的臨時變數tmpNum
  }

  return innerFuncTest; //返回內建函式
}

//調用函數
var myFuncTest=funcTest(); 
myFuncTest();//彈出100

上面代碼中,注釋已經寫的清清楚楚。現在我們可以這麼理解“閉包”:在函數體內定義另外的函數作為目標對象的方法函數(樣本中就是在函數funcTest內定義另外的函數innerFuncTest作為funcTest的方法函數),而這個對象的方法函數反過來引用外層函數體中的臨時變數(閉包是一種間接保持變數值的機制。樣本中就是內建函式innerFuncTest引用外層函數funcTest的臨時變數tmpNum,這裡必須注意,臨時變數可以包括外部函數中聲明的所有局部變數參數和聲明的其他內建函式)。當其中一個這樣的內建函式在包含它們的外部函數之外被調用時,就會形成閉包(樣本中,調用函數的時候,myFuncTest實際調用的是innerFuncTest函數,也就是說funcTest的一個內建函式innerFuncTest在funcTest之外被調用,這時就建立了一個閉包)。
2、兩個利用閉包的例子
下面舉兩個例子,一個是因為閉包導致了問題,而另一個則利用閉包巧妙地通過函數的範圍綁定參數。
這兩個例子相關的HTML標記片斷如下:
<a href="#" id="closureTest0">利用閉包的例子(1秒後會看到提示)</a><br />
<a href="#" id="closureTest1">由於閉包導致問題的例子1</a><br />
<a href="#" id="closureTest2">由於閉包導致問題的例子2</a><br />
<a href="#" id="closureTest3">由於閉包導致問題的例子3</a><br />
(1)、因閉包而導致問題
上面的HTML標記片斷中有4個<a>元素,現在要給後三個指定事件處理常式,使它們在使用者單擊時報告自己在頁面中的順序,比如:當使用者單擊第2個連結時,報告“您單擊的是第1個連結”。為此,如果編寫下列為後三個連結添加事件處理常式的函數:

Code
function badClosureExample(){
    for (var i = 1; i <4; i++) {
        var element = document.getElementById('closureTest' + i);
        element .onclick = function(){
            alert('您單擊的是第' + i + '個連結');
        }
    }
}

然後,在頁面載入完成後(不然可能會報錯)調用該函數:
window.onload = function(){
    badClosureExample();
}
看一下運行結果,此時單擊後3個連結,會看到警告框中顯示什麼資訊呢?——全都是“您單擊的是第4個連結”。是不是令你感到十分意外?為什嗎?
分析:因為在badClosureExample()函數中指定給element.onclick的事件處理常式,也就是onclick那個匿名函數是在badClosureExample()函數運行完成後(使用者單擊連結時)才被調用的。而調用時,需要對變數i求值,解析程式首先會在事件處理常式內部尋找,但i沒有定義。然後,又到 badClosureExample()函數中尋找,此時有定義,但i的值是4(只有i大於4才會停止執行for迴圈)。因此,就會取得該值——這正是閉包(匿名函數)要使用其外部函(badClosureExample)範圍中變數的結果。而且,這也是由於匿名函數本身無法傳遞參數(故而無法維護自己的範圍)造成的。
那麼這個例子的問題怎麼解決呢?其實方法有很多(自己不妨寫一下看看),我認為比較簡單直接的代碼:

Code
function popNum(oNum){
    return function(){
                    alert('您單擊的是第'+oNum+'個連結');
   }
}
function badClosureExample(){
    for (var i = 1; i <4; i++) {
        var element = document.getElementById('closureTest' + i);
        element .onclick =new popNum(i);
        }
}

(2)、巧妙利用閉包綁定參數
 還是上面的HTML片段,我們要在使用者單擊第一個連結時延時彈出一個警告框,怎麼實現?答案是使用setTimeout()函數,這個函數會在指定的毫秒數之後調用一個函數,如:
setTimeout(someFunc,1000);
但問題是,無法給其中的someFunc函數傳遞參數。而使用閉包則可以輕鬆解決這個問題:

function goodClosureExample(oMsg){
    return function(){
        alert(oMsg);
    };
}

函數goodClosureExample用來返回一個匿名函數(閉包)。而我們可以通過為它傳遞參數來使返回的匿名函數綁定該參數,如:
var good = goodClosureExample('這個參數是通過閉包綁定的');
而此時,就可以將綁定了參數的good函數傳遞給setTimeout()實現延時警告了:
setTimeout(good,1000) //此時good中已經綁定了參數
最後,測試通過的完整代碼:

Code
window.onload = function(){
    var element = document.getElementById('closureTest0');
    if (element) {
        var good = goodClosureExample('這個參數是由閉包綁定的');
        element.onclick = function(){
            setTimeout(good, 1000); //延遲1秒彈出提示
        }
    }
}

3、javascript的記憶體回收原理
(1)、在javascript中,如果一個對象不再被引用,那麼這個對象就會被GC回收;
(2)、如果兩個對象互相引用,而不再被第3者所引用,那麼這兩個互相引用的對象也會被回收。
在js中使用閉包,往往會給javascript的記憶體回收行程製造難題。尤其是遇到對象間複雜的循環參考時,記憶體回收的判斷邏輯非常複雜,搞不好就有記憶體流失的危險,所以,慎用閉包。ms貌似已經不建議使用閉包了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.