假如我們需要制作一個計數器,每點擊一次就加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);
}
}