javascript閉包(Closure)初探

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


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以外被調用,這時就建立了一個閉包)。
二、兩個利用閉包的例子
下面舉兩個例子,一個是由於閉包致使了問題,而另外一個則利用閉包巧妙地經過函數的做用域綁定參數。
這兩個例子相關的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個連接」。爲此,若是編寫下列爲後三個連接添加事件處理程序的函數: java


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)做用域中變量的結果。並且,這也是因爲匿名函數自己沒法傳遞參數(故而沒法維護本身的做用域)形成的。
那麼這個例子的問題怎麼解決呢?其實方法有不少(本身不妨寫一下看看),我認爲比較簡單直接的代碼: 閉包


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中已經綁定了參數
最後,測試經過的完整代碼: 測試


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

三、javascript的垃圾回收原理
(1)、在javascript中,若是一個對象再也不被引用,那麼這個對象就會被GC回收;
(2)、若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。
在js中使用閉包,每每會給javascript的垃圾回收器製造難題。尤爲是遇到對象間複雜的循環引用時,垃圾回收的判斷邏輯很是複雜,搞很差就有內存泄漏的危險,因此,慎用閉包。ms貌似已經不建議使用閉包了。 spa

相關文章
相關標籤/搜索