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