理解JS閉包——以計數器為例


假如我們需要制作一個計數器,每點擊一次就加1。代碼寫成這樣:

var counter = 0;      //把計數器counter設置成全局變量
function add(){
   return counter+=1;    
}
add();   //1
add();   //2
add();   //此時counter=3

每執行add()函數一次,相當於執行 counter=counter+1 一次,等於是為counter重新賦值,這樣執行過3次add()后,因為counter是全局變量,所以此時counter為3,實現了計數器的功能。但這樣有一個問題,即counter暴露出來了,其他函數或者動作也能改變它的值。如:

var counter = 0;
function add(){
   return counter+=1;
}
add();
add();
add();   
//  counter為3

counter++;
//counter為4

 這樣的計數器是不安全也不符合要求的。但是如果將counter設置在一個函數體內,那么因為counter是函數局部變量,每次調用的時候counter都會恢復成初始值。如:

function add(){
   var counter=0;
   counter+=1;
}
add();   //counter為1
add();   //counter為1
add();   //counter為1

 所以我們需要的是這么一個變量counter,它在函數每次調用時不要重置,但是它存在於函數內部。

在JavaScript中,所有函數都能訪問它們上一層的作用域。所以在父函數里定義子函數,子函數能訪問父函數的變量。子函數就相當於閉包。如果把計數器counter定義在父函數內,然后在子函數調用計數器counter,再在父函數外面執行子函數進行計數。那么這個計數器只能通過嵌套函數訪問到,並且每次計數都不會重置(因為我們執行的是子函數,只有執行父函數時該計數器才會重置)。

這個思想很好,但是如果我們把代碼寫成這樣:

//例1

var add = function(){
      var counter = 0;
      return function(){
             return(++counter);
      }
};
add()();   //counter為1
add()();   //counter為1
add()();   //counter為1

無論調用add()函數多少次,都返回1,不能實現累加效果。這是為什么呢?

在例1中,add函數的執行順序為:

1、將counter 設置為0;

2、返回一個函數 function(){return(++counter)}。

所以每次調用add函數將重復1,2兩個步驟,相當於依然從重置了counter。

注意:要調用add函數,實現++counter,需要這樣操作 :add()()。這是為什么呢?且看下圖:

如果只執行add()。那么就將按照1,2的順序執行,最終的結果就是返回一個函數function(){return(++count)}。

所以要實現++counter,必須得對add()執行一次后的結果(就是函數function(){++count})再次調用,即調用add(),也即add()()。

但是每次調用add()(),執行的順序就是:

1、將counter 設置為0;

2、返回一個函數 function(){++counter};

3、執行函數function(){++counter}。

每次調用都重置了counter。

結論:對於一個有閉包的函數來說,如果他有一層嵌套(即函數中嵌套一個閉包函數),那么要調用這個函數,需要兩個()()。因為閉包中返回的是一個函數,調用時只用一個(),結果就是返回閉包的那個函數,而不是閉包函數的執行結果。而實際上我們需要的是執行閉包的那個函數后的結果。

再看下面這段代碼:

//例2

var add = (function(){
      var counter = 0;
      return function(){
             return(++counter);
      }
})();      //這里add已經是執行過后的函數了。即add指定了函數自我調用的返回值
add();   //counter為1
add();   //counter為2
add();   //counter為3

在例2中,變量add指定的是函數自我調用的返回值(指定一個函數並立即執行該函數),即例2中的add是執行了例1中add函數后返回的結果。也就是說,add實際上就是函數function(){++function}。看下圖:

第一次調用add(),相當於執行了function(){return (++counter)}一次;

第二次調用add(),相當於又執行了function(){return (++counter)}一次;

又由於function(){return (++counter)}是閉包,引用了其父函數的變量counter,所以在函數調用完畢counter依然存在,沒有清零。

而且只在第一次給add賦值時,將counter設置為0。以后每次調用add(),counter都自增一次,沒有重置。也就是前文說的,父函數只執行一次,每次調用都只執行了子函數。

這是為什么呢?因為只有執行下面這個函數,counter才重置。而這個函數只在第一次給add賦值時執行過一次。以后每次調用add(),都相當於調用function(){return(++counter)}。

function(){
   var counter = 0;

   return function(){
      return (++counter);
   }
}


免責聲明!

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



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