【Js閉包】由一道面試題簡單擴展


在一個前端公眾號,看到這么一個號稱簡單的面試題:

1、以下程序輸出什么?

<script type="text/javascript">
    function init(){
        for (var i = 0; i < 10; ++i) {
            setTimeout(function () { 
             console.log(i);
            }, 0);
        }
         
    }
    window.onload=init;
</script>

2、若需要輸出0123456789,應該怎么修改?

結果,輸出的為10101010101010101010

若要輸出0123456789,則可以將代碼改成

<script type="text/javascript">
    function init2(){
        for (var i = 0; i < 10; ++i) {
            setTimeout(
                (function(i){
                    return function(){
                        console.log(i)
                    }
                })(i), 0);
        }
    }
    window.onload=init2;
</script>

解釋:

1、for循環每次注冊一個延遲函數, setTimeout是異步的,傳入事件隊列中,在循環結束后進行處理,當循環結束時,i為10。

setTimeout中的匿名function沒有將 i 作為參數傳入來固定這個變量的值, 讓其保留下來, 而是直接引用了外部作用域中的 i, 因此 i 變化時, 也影響到了匿名function.

2、for循環執行時,給點擊事件綁定的匿名函數傳遞i后,立即執行返回一個內部的匿名函數,因為參數是按值傳遞的,

     所以此時形參保存的就是當前i的值,內部的匿名函數一直保存着當前i的值。 返回的匿名函數執行彈出各自保存的 i 的引用的值。

 

看到解釋到這里,我是一臉懵逼,關鍵在於閉包的參數傳遞,所以我自己對閉包的知識進行了一下下擴展。

0、前言:Js變量的作用域

     變量的作用域包括:全局變量、局部變量

     Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。

   var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999

    在函數外部無法讀取函數內部的局部變量

  function f1(){
    var n=999;
  }
  alert(n); // error

    注意:若函數內部聲明變量的時候,一定要用var,否則實際上聲明了全局變量,

    會造成其他函數誤用變量、全局對象過於龐大,影響訪問速度等不良影響

 function f1(){
    n=999;
  }
  f1();
  alert(n); // 999

Javascript語言特有的"鏈式作用域"結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立

1、閉包的解釋

  閉包是指有權訪問另一個函數作用域變量的函數,創建閉包的通常方式,是在一個函數內部創建另一個函數。

    閉包的本質是一個函數,將函數內部和外部連接起來的橋梁。

2、閉包的寫法

      好像,還是,有那么點抽象,那就網上找個例子:

function func1(){
        var age = 22;
        function func2(){
            return age;
        }
        return func2;
    }
    var result = func1();      //得到的是函數func2這一整個函數
    var _num = result();       //得到的是func2的執行結果也就是age的值
    console.log(result);       //function func2(){ return age; }
    console.log(_num);         //22

       結果分析:

       a)首先要明白一個函數名稱加不加括號的區別,比如說func1和func1(),func1表示的是這個函數本身,func1()表示的是這個名叫func1的函數的執行結果也就是它的返回值。

       b) 其次我們來分析閉包的原理,因為func2是函數func1的子函數,所以在func2中可以訪問到變量age,並將這個值作為函數func2的返回值返回,最后將整個func2函數作為func1函數的返回值。

       c)最后我們來分析一下result和_num的值,result是函數func1的執行結果,也就是func1的返回值func2函數本身,_num的值為func2的執行結果也就是變量age。

       d)至此,我們實際上已經將局部變量age的值獲取到了並存在變量_num中,實現了在函數外部訪問局部變量的值的問題。

3、閉包的用途

    a) 匿名自執行函數

  創建了一個匿名的函數,並立即執行它,由於外部無法引用它內部的變量,因此在執行完后很快就會被釋放,關鍵是這種機制不會污染全局對象。

         ① 匿名函數:顧名思義,就是沒有方法名的函數

         ② 匿名函數調用方式:

             使用()將匿名函數括起來,然后后面再加一對小括號(包含參數列表)  

       (function(a,b)
        {
           console.log('匿名函數加圓括號:'+(a+b));
        }(1,2));

            將匿名函數賦值給一個變量,通過變量引用進行函數調用

       var noName= function(a,b){

console.log('匿名函數賦值變量:'+(a+b));

}(1,4);

    b) 緩存(未整理驗證)

再來看一個例子,設想我們有一個處理過程很耗時的函數對象,每次調用都會花費很長時間,
那么我們就需要將計算出來的值存儲起來,當調用這個函數的時候,首先在緩存中查找,如果找不到,則進行計算,
然后更新緩存並返回值,如果找到了,直接返回查找到的值即可。閉包正是可以做到這一點,因為它不會釋放外部的引用,
從而函數內部的值可以得以保留。

 

   c)實現封裝

       可以先來看一個關於封裝的例子,在person之外的地方無法訪問其內部的變量,而通過提供閉包的形式來訪問

 

   d)面向對象編程

      實例獨立訪問成員變量,互不影響


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM