首頁 > web前端 > js教程 > 主體

深入理解JavaScript 閉包究竟是什麼_基礎知識

WBOY
發布: 2016-05-16 17:37:26
原創
860 人瀏覽過

1.簡單的例子

先從一個經典錯誤談起,頁面上有若干個div, 我們想給它們綁定一個onclick方法,於是有了下面的程式碼

複製代碼 代碼如下:


        0 1 span>2 3
   

   

        0 1     span> 2 3
   


複製程式碼



複製程式碼



複製程式碼


複製程式碼

$(document).ready(function() {
           for ( var i = 0; i                 spans[i].onclick = function() {                }
            }
          }
       );


很簡單的功能可是卻偏偏出錯了,每次alert出的值都是4,簡單的修改就好使了



複製程式碼

程式碼如下:


var spans2 = $("#divTest2 span");

        $(document).ready(function() {            for (var i = 0; i                     spans2[i]. @                   alert(num);            }    2.內部函數

讓我們從一些基礎的知識談起,首先了解一下內部函數。內部函數就是定義在另一個函數中的函數。例如:



複製程式碼

程式碼如下:}innerFn就是一個被包在outerFn作用域中的內部函數。這意味著,在outerFn內部呼叫innerFn是有效的,而在outerFn外部呼叫innerFn則是無效的。下面程式碼會導致一個JavaScript錯誤:




複製碼


程式碼如下:
function outerFn() {");            function innerFn() {                }        }        innerFn();
不過在outerFn內部呼叫innerFn,則可以成功運行:

複製碼 程式碼如下:

function outerFn() {");
            function innerFn() {
                }
            innerFn();
        }
        outerFn();


2.1偉大的逃脫

JavaScript允許開發人員像傳遞任何類型的資料一樣傳遞函數,也就是說,JavaScript中的內部函數能夠逃脫定義他們的外部函數。

逃脫的方式有很多種,例如可以將內部函數指定給一個全域變數:


複製程式碼 程式碼如下:
var globalVar;
        function outerFn() {
       
            function innerFn() {
                document. write("Inner function
");
            }
              outerFn();
        globalVar();


呼叫outerFn時會修改全域變數globalVar,這時候它的引用變成innerFn,此後呼叫globalVar和呼叫innerFn一樣。這時在outerFn外部直接呼叫innerFn仍然會導致錯誤,這是因為內部函數雖然透過把引用保存在全域變數中實現了逃脫,但這個函數的名字依然只存在於outerFn的作用域中。

也可以透過在父函數的回傳值來獲得內部函數參考

複製碼

程式碼如下:function outerFn() {");            function innerFn() {
                }
            return innerFn;
        }
        var fnRef = outerFn();
        fnRef();


這裡並沒有在全局透過呼叫outerFn能夠獲得這個引用,而且這個引用可以可以保存在變數中。


這種即使離開函數作用域的情況下仍然能夠透過引用呼叫內部函數的事實,意味著只要存在呼叫內部函數的可能,JavaScript就需要保留被引用的函數。而且JavaScript在運行時需要追蹤引用這個內部函數的所有變量,直到最後一個變數廢棄,JavaScript的垃圾收集器才能釋放對應的記憶體空間(紅色部分是理解閉包的關鍵)。

說了半天總算和閉包有關係了,閉包是指有權限訪問另一個函數作用域的變數的函數,創建閉包的常見方式就是在一個函數內部創建另一個函數,就是我們上面說的內部函數,所以剛才說的不是廢話,也是閉包相關的^_^


1.2變數的作用域

內部函數也可以有自己的變量,這些變數都被限制在內部函數的作用域:


複製程式碼


複製程式碼 程式碼如下:

function outerFn() {
            document.write("Outer function
");
in               var innerVar = 0;
                innerVar ;
                document.write("Inner functiont");
                    }
            return innerFn;
     fnRef = outerFn();
        fnRef();
        fnRef();
  fnRef2();
        fnRef2();


每當透過引用或其它方式呼叫這個內部函數時,就會建立一個新的innerVar變量,然後加1,最後顯示


複製程式碼

程式碼如下:Outer functionInp >Inner function    innerVar = 1Outer function
Inner function    innerVar = 1
Inner function    innerVar = 1






複製程式碼

程式碼如下:

            var fnRef = outerFn();
        fnRef();
        fnRef( );
        var fnRef2 = outerFn();
        fnRef2();
       fnRef2();
       fnRef2();
      這個全域變數的值:





複製程式碼


程式碼如下:

Outer functionInner function    globalVar = 2
Outer functionInner function    globalVar = 3

Inner function    globalVar = 4

複製程式碼


程式碼如下:

function outerFn() {
            var outerVar = 0;
             function innerFn() {
                outerVar ;
                document.write("Inner functiont");
        >            }
            return innerFn;
     fnRef = outerFn();
        fnRef();
        fnRef();
  fnRef2();
        fnRef2();


這次結果很有趣,也許或出乎我們的意料


複製程式碼

程式碼如下:Outer functionInner function    outerVar = 2Outer function
Inner function    outerVar = 1
Inner function    outerVar = 2
Inner function    outerVar = 2
Inner 🎜>每個引用調用innerFn都會獨立的遞增outerVar。也就是說第二次呼叫outerFn沒有繼續沿用outerVar的值,而是在第二次函數呼叫的作用域建立並綁定了一個新的outerVar實例,兩個計數器完全無關。

當內部函數在定義它的作用域的外部被引用時,就創建了該內部函數的一個閉包。這種情況下我們稱既不是內部函數局部變量,也不是其參數的變數為自由變量,稱外部函數的呼叫環境為封閉閉包的環境。從本質上講,如果內部函數引用了位於外部函數中的變量,則相當於授權該變數能夠被延遲使用。因此,當外部函數呼叫完成後,這些變數的記憶體不會被釋放(最後的值會保存),閉包仍然需要使用它們。

 

3.閉包之間的互動

當存在多個內部函數時,很可能會出現意料之外的閉包。我們定義一個遞增函數,這個函數的增量為2

複製程式碼

程式碼如下:


function externalFn() {
            var outerVar = 0;
            function innerFn1() {
                外在變項中;
                document.write("內部函數1t");
                     }

            function innerFn2() {               .write("內部函數2t");

                document.write("outerVar = "outerVar “
”) ;
            }
            return { "fn1": insideFn1, "fn   var fnRef = externalFn();
        fnRef.fn 1();
        fnRef . fn2();
        fnRef.fn1();
        var fnRef2 = externalFn();        fnRef2. fn1() ;


我們映射回傳兩個內部函數的引用,可以透過傳回的引用呼叫任一個內部函數,結果:




複製程式碼

程式碼如下:

外部函數內部函數1 🎜>內部函數2    externalVar = 3內部函數1    externalVar = 4外部函數內部函數1    externalVar = 1內部函數1   Var = 4

innerFn1和innerFn2引用了同一個局部變量,因此它們共享一個封閉環境。當innerFn1為outerVar遞增一時,久違innerFn2設定了outerVar的新的起點值,反之亦然。我們也看到對outerFn的後續呼叫也會創建這些閉包的新實例,同時又創建新的封閉環境,本質上就是創建了一個新對象,自由變量就是這個對象的實例變量,而閉包就是這個物件的實例變數實例方法,而且這些變數也是外部的,因為不能在封裝中引用它們的作用域外部直接這些變量,從而保證了針對物件資料的原生性。


4.解惑


現在我們可以初步看看開頭寫的例子就很容易明白為什麼第一種寫法每次都會alert 4了。


複製程式碼

程式碼如下:


for (var i = 0; i            spans[i].onclick = function() {                 }
上面程式碼在頁面載入後就會執行,當i的值為4的時候,判斷條件不成立,for迴圈執行完畢,但是因為每個span的onclick方法這時候為內部函數,所以i被閉包引用,記憶體不能被銷毀,i的值會一直保持4,直到程式改變它或所有的onclick函數銷毀(主動把函數賦為null或頁面卸載)才會被回收。這樣每次我們點擊span的時候,onclick函數會找i的值(作用域鍊是引用方式),一查等於4,然後就alert給我們了。而第二種方式是使用了一個立即執行的函數又創建了一層閉包,函數聲明放在括號內就變成了表達式,後面再加上括號括號就是調用了,這時候把i當參數傳入,函數立即執行,num保存每次i的值。

這一通下來想必大家也和我一樣,對閉包有所了解了吧,當然完全了解的話需要把函數的執行環境和作用域鏈搞清楚 ^_^

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!