我所理解的JavaScript閉包

1、閉包(Closure)

1.一、什麼是閉包?

理解閉包概念:css

a、閉包是指有權限訪問另外一個函數做用域的變量的函數,建立閉包的常見方式就是在一個函數內部建立另外一個函數,也就是建立一個內部函數,建立一個閉包環境,讓返回的這個內部函數保存要引用的變量,以便在後續執行時能夠保持對這個變量的引用。html

b、只要存在調用內部函數的可能,JavaScript就須要保留被引用的函數。並且JavaScript運行時須要跟蹤引用這個內部函數的全部變量,直到最後一個變量廢棄,JavaScript的垃圾收集器才能釋放相應的內存空間。java

c、Javascript語言特有的"鏈式做用域"結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量。安全

d、若是一個內部函數被調用且引用了它的外部變量那麼它就是一個閉包。閉包

 

相信你看了上面的這段話可能還不理解什麼是閉包,那麼我就舉一個閉包的經典例子來幫助你理解閉包的概念吧。函數

請看下面這段代碼: post

複製代碼
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
    </head>
    <body>
        <script type="text/javascript">
            function out() {
                var i = 0;
                function inner() {
                    alert(++i);
                }
                return inner;
            }
            var  ref= out();
            ref();
        </script>
    </body>
</html>
複製代碼

結果:性能

上面的代碼有兩個特色:
一、建立兩個函數out,inner,函數inner嵌套在函數out內部,也能夠說是函數inner是函數out的內部函數;
二、調用函數out返回內部函數inner。
這樣在執行完var ref=out()後,變量ref其實是指向了函數inner,也能夠說是引用了函數inner,再執行ref()後就會看到上圖彈出一個窗口顯示i的值第一次爲1。ui

這段代碼其實就建立了一個閉包,爲何?由於函數out外的變量ref引用了函數out內部的函數inner,也就是說:

當函數out的內部函數inner被函數out外的一個變量ref引用的時候,就建立了一個閉包。

可能你仍是不理解閉包這個概念,由於你不知道閉包有什麼用,那麼先理解一下閉包的做用吧。

1.二、爲何要用閉包(做用)?

1.2.一、保護函數內的變量安全。

解釋:以上面的的例子爲例,函數out中i只有函數innder才能訪問,而沒法經過其餘途徑訪問到,所以保護了i的安全性。

1.2.二、經過訪問外部變量,一個閉包能夠暫時保存這些變量的上下文環境,當引用完畢後纔會銷燬。

在上面的示例中增長了一次函數ref()的調用,執行的結果以下:

解釋:當函數out執行完而且最終退出時,它的局部變量會被Javascript的垃圾回收機制回收所佔用的資源,也就是局部變量被銷燬,可是由於建立了閉包環境,那麼內部函數inner就會暫時保存外部函數的局部變量上下文環境。不會被垃圾回收機制回收。因此函數out中的i被複制一份暫時保存下來,這樣每次執行ref(),i都是自加1後alert輸出i的值。當變量ref引用內部函數inner完成結束後,最後纔會被回收機制回收。這只是我對閉包做用的簡單初淺理解,不專業也不嚴謹,但大概意思就是這樣,理解閉包須要按部就班的過程。

相信你看了閉包的做用,對理解什麼是閉包是否更明白一些,若是仍是很疑惑,那麼我就再舉幾個閉包的經典例子來幫助你理解閉包的概念吧。

1.三、閉包的經典示例

1.3.一、示例一

問題:假如咱們有以下需求請在頁面中放10個div,每一個div寫上對應的數字,當點擊每個div時顯示索引號,如第1個div顯示0,第10個div顯示9。

可能你會這樣作:

複製代碼
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
        <style type="text/css">
            div {
                width: 50px;
                height: 50px;
                background: lightcoral;
                float: left;
                margin: 20px;
                font: 15px/50px "microsoft yahei";
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div>div-1</div>
        <div>div-2</div>
        <div>div-3</div>
        <div>div-4</div>
        <div>div-5</div>
        <div>div-6</div>
        <div>div-7</div>
        <div>div-8</div>
        <div>dvi-9</div>
        <div>div-10</div>
        <script type="text/javascript">
            var divs=document.getElementsByTagName("div");
            for (var i=0;i<divs.length;i++) {
                divs[i].onclick=function(){
                    alert(i);
                }
            }
        </script>
    </body>
</html>
複製代碼

 結果:

從上面的結果你會發現,無論你點擊了哪一個div,彈出的框div索引老是10,這可能會讓你很意外,會產生疑惑,爲何會出現這樣的結果呢?

解釋:由於點擊事件的函數內部使用外部的變量i一直在變化,當咱們指定click事件時並無保存i的副本,這樣作也是爲了提升性能,但達不到咱們的目的,咱們要讓他執行的上下文保存i的副本,這種機制就是閉包。

使用閉包能夠解決此問題,代碼作了以下修改:

複製代碼
<script type="text/javascript">
            var div=document.getElementsByTagName("div");
                for (var i = 0; i < div.length; i++) {
                    div[i].onclick=function(n){
              return function(){ alert(n);//產生閉包,引用外部變量。 } }(i); } </script>
複製代碼

結果: 

 

從上面的結果你會發現,使用閉包後,就達到你預期的結果了。

解釋:n是外部函數的值,可是內部函數須要使用這個值,由於產生閉包執行環境,因此返回函數前的n被臨時駐留在內存中給點擊事件使用,簡單說就是函數的執行上下文被保存起來,i生成了多個副本。

 1.3.二、示例二

 示例代碼:

複製代碼
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            function out() {
                var i = 10;
                return function inner() {
                    i++;
                    alert(i);//引用了外部變量,建立了閉包環境
                };
            }
            //此處爲函數調用,第一個括符爲調用out方法,第二個括符爲調用返回的inner方法。
            out()();
        </script>
    </body>
</html>
複製代碼

 

運行 結果:

1.3.三、示例三

問題:不使用閉包的狀況下,無論執行多少次,結果都同樣是3.

示例代碼:

複製代碼
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
    </head>
    <body>
        <script type="text/javascript">
            var arr = [1, 2, 3];
            var obj = {};
            var exp = function() {
                for(var i = 0; i < arr.length; i++) {
                    obj[i] = function() {
                        console.log(i);
                    };
                }
            }
            exp();
            var fn0 = obj[0];
            var fn1 = obj[1];
            var fn2 = obj[2];
            fn0(); //輸出3
            fn1(); //輸出3
            fn2(); //輸出3
        </script>
    </body>
</html>
複製代碼

運行結果:

使用閉包後的示例代碼:

複製代碼
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
    </head>
    <body>
        <script type="text/javascript">
            var arr = [1, 2, 3];
            var obj = {};
            var exp = function() {
                for(var i = 0; i < arr.length; i++) {
                    (function(i) {
                        obj[i] = function() {
                            console.log(i);
                        };
                    })(i); //i做爲參數傳給當即調用函數
                }
            };
            exp();
            var fn0 = obj[0];
            var fn1 = obj[1];
            var fn2 = obj[2];
            fn0(); //輸出0
            fn1(); //輸出1
            fn2(); //輸出2
        </script>
    </body>
</html>
複製代碼

運行結果:

 

 上面代碼解釋:

  • 在for循環裏建立了一個當即調用函數表達式
  • fn0,fn1,fn2分別指向了匿名函數的引用
  • fn0(),fn1(),fn2()都訪問了i(這個i是位於這個匿名函數的上層做用域鏈,它會被保存在內存中,對於每個函數引用來講i是惟一的)

總結:

相信經過以上是幾個閉包示例,你對閉包也有必定的理解了吧。限於本人才疏學淺,對閉包的理解也並非很透徹,只是理解了一些表面,會使用而已。

若你想理解的更深刻推薦你去看老外對閉包的解釋:http://stackoverflow.com/questions/111102/how-do-javascript-closures-work

 

做者: 欲淚成雪
QQ:1521274639 點擊這裏給我發消息
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索