1. 程式人生 > >JS中的閉包和this

JS中的閉包和this

關於閉包,每次看書之後總是覺得自己理解了,可以隔一段時間之後,又總是容易混淆,所以還是記錄一下!!

閉包即一個函式有權訪問另一個函式作用域中的變數。

  每當定義一個函式的時候,會建立一個預先包含全域性變數物件的作用域鏈,這個作用域鏈被儲存在內部的[[Scope]]屬性中。當呼叫該函式的時候,會為函式建立一個執行環境(每個函式都有自己的執行環境。當執行流進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。)及相應的作用域鏈(先通過複製函式的[[Scope]]屬性中的物件構建起執行環境的作用域鏈,然後建立函式的活動物件(使用arguments和其他命名引數的值來初始化函式的活動物件)並推入執行環境作用域鏈的最前端)。一般來講,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅僅儲存全域性作用域(全域性執行環境的活動物件)。但是,閉包的情況又有所不同。

  匿名函式的執行環境具有全域性性,因此其this物件通常指向Window。但匿名函式作為返回值從另一個函式return時,它的作用域鏈被初始化為包含 包含函式的活動物件和全域性變數物件。這樣,匿名函式可以訪問在包含函式中定義的所有變數。而且當該函式執行完畢後,該函式的執行環境和作用域鏈都會被銷燬,但他的活動物件不會被銷燬,仍然會留在記憶體中,因為匿名函式的作用域鏈仍然在引用這個活動物件。直到匿名函式被銷燬後(設定匿名函式的引用為null),該函式的活動物件才被銷燬。

1.作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函式中任何變數的最後一個值。因為閉包所儲存的是整個變數物件,而不是某個特殊的變數。如下程式碼:

 1   function createFunctions(){
 2     var result = new Array();
 3     for (var i=0; i < 10; i++){
 4       result[i] = function(){ 
 5         console.log('this:'+this); //this:object Window
 6         return i;
 7       };
 8     }
 9     return result; //返回匿名函式的陣列,共10個
10   }
11 
12   var array = createFunctions();
13 console.log(array); //(10) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ] 14 15 array.forEach(function(item,index){ 16 var data = item(); //呼叫匿名函式 17 console.log(index,data); //列印10次10 18 }) 19

 

2.兩次呼叫createFunctions()函式返回的陣列(因為result是區域性變數,兩次呼叫createFunctions()函式都會有自己的活動物件即變數)是不同的,且陣列中包含的匿名函式也是不同的:

1   var array1 = createFunctions();
2   console.log(array[0]===array[0]);  //true:array中的第一個匿名函式與自己是相同的地址
3   console.log(array[0]===array[1]);  //false:array中的第一個匿名函式與第二個匿名函式不是同一地址
4 
5   console.log(array===array1);  //false:陣列array不等於array1,即它們指向的陣列首地址不相同,每次執行createFunctions()返回result的地址不相同  
6   console.log(array[0]===array1[0]);  //false:array中的第一個匿名函式與array1中的第一個匿名函式不是同一地址

 

3.將陣列中的匿名函式改為命名函式,因為該命名函式為區域性作用域,每次呼叫createFunctions()函式時會重新建立活動物件,包括重新建立命名函式,所以兩次呼叫createFunctions()函式返回的陣列是不同的,且陣列中包含的命名函式也是不同的:

 1   function createFunctions1(){
 2     var result = new Array();
 3     for (var i=0; i < 10; i++){
 4       result[i] = function log_num(){ 
 5         console.log('this:'+this); //this:object Window
 6         return i;
 7       };
 8     }
 9     return result; //返回匿名函式的陣列,共10個
10   }
11 
12   var array2 = createFunctions1();
13   var array3 = createFunctions1();
14   console.log(array2[0]===array2[0]);  //true:array2中的第一個命名函式與自己是相同的地址
15   console.log(array2[0]===array2[1]);  //false:array2中的第一個名函式與第二個名函式不是同一地址
16 
17   console.log(array2===array3);  //false:陣列array2不等於array3,即它們指向的陣列首地址不相同,每次執行createFunctions()返回result的地址不相同  
18   console.log(array2[0]===array3[0]);  //false:array2中的第一個名函式與array3中的第一個名函式不是同一地址

 

4.將陣列中的區域性命名函式改為全域性命名函式,每次呼叫createFunctions()函式時返回的陣列中都包含同一命名函式,雖然兩次呼叫createFunctions()函式返回的陣列是不同的,且陣列中包含的命名函式是相同的:

 1   function log_num2(){ //全域性命名函式
 2     console.log('this:'+this); //this:object Window
 3     return i; 
 4   };
 5 
 6   function createFunctions2(){
 7     var result = new Array();
 8     for (var i=0; i < 10; i++){
 9       result[i] = log_num2; //引用全域性命名函式
10     }
11     return result; //返回匿名函式的陣列,共10個
12   }
13 
14   var array2 = createFunctions2();
15   var array3 = createFunctions2();
16   console.log(array2[0]===array2[0]);  //true:array中的第一個命名函式與自己是相同的地址
17   console.log(array2[0]===array2[1]);  //true:array中的第一個命名函式與第二個命名函式是同一地址
18   console.log(array2[0]===array2[9]);  //true:array中的第一個命名函式與第十個命名函式是同一地址
19 
20   console.log(array2===array3);  //false:陣列array2不等於array3,即它們指向的陣列首地址不相同,每次執行createFunctions()返回result的地址不相同  
21   console.log(array2[0]===array3[0]);  //true:array2中的第一個命名函式與array3中的第一個命名函式是同一地址

 

5.但是在全域性命名函式這種情況下,實際上已經不是閉包,因為result返回的陣列中包含的函式是全域性作用域的,並不能訪問包含函式的作用域。所以通過呼叫命名函式是,出現未定義變數i的告警:

1   array2.forEach(function(item,index){
2     var data = item(); //呼叫命名函式列印Uncaught ReferenceError:i is not defined
3     console.log(index,data);  //未執行
4   })

 

總結:

1.函式內部的一個特殊物件是 this , this引用的是函資料以執行的環境物件,當在網頁的全域性作用域中呼叫函式時,this 物件引用的就是 window。一般情況下,匿名函式的執行環境為window,即匿名函式內的this指向window。this指向的執行環境是呼叫函式的環境,而不是函式執行時的活動物件(即作用域鏈的最前端)。

2.包含函式執行完成後才產生閉包,此時變數物件為最後一個值,所以當執行閉包時只能取得包含函式中任何變數的最後一個值。

3.通過執行var array2 = createFunctions2();var array3 = createFunctions2();,array2和array3引用的 createFunctions2()函式的活動物件是不同的,活動物件都是 createFunctions2()函式執行的時候建立的。

4.通過呼叫外部函式,在區域性作用域中建立的命名函式,每次建立的活動物件不同,所以命名函式也是不同的,雖然名字相同,但地址不同。