談到閉包,人們常常會把匿名函數和閉包混淆在一起。閉包是指由權訪問另一個函數作用域中的變數的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數,仍以前面的 createComparisonFunction()函數為例 在標識的部分,它訪問了外部的變數 propertyName 即使這個函數被返 ...
談到閉包,人們常常會把匿名函數和閉包混淆在一起。閉包是指由權訪問另一個函數作用域中的變數的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數,仍以前面的
createComparisonFunction()函數為例
1 function createComparisonFunction(propertyName){ 2 return function(object1,object2){ 3 var value1 = object1[propertyName]; 4 var value2 = object2[propertyName]; 5 if(value1<value2){ 6 return -1; 7 }else if(value1>value2){ 8 return 1; 9 }else{ 10 return 0; 11 } 12 } 13 };
在標識的部分,它訪問了外部的變數 propertyName 即使這個函數被返回後,仍然可以訪問到這個變數(propertyName);這是應為 匿名函數作用域鏈中包含 createComparisonFunction() 的作用域。
要理解必須掌握作用域的幾個概念
作用域鏈:
什麼時候會生成?:當代碼在一個執行環境中執行時,就會創建變數對象的一個作用域鏈。
作用:保證對執行環境有權訪問的所以函數和變數的有序訪問。作用域的前端永遠是當前的執行環境所對應的變數對象;
執行環境:定義了變數或函數有權訪問其他數據,決定它們各自的行為。 每個環境都有一個與之對應的變數對象(variable object)。環境中定義的變數和函數都保存在這個對象中。 當函數執行完成後其執行環境就會被銷毀
變數對象:保存執行環境中的變數和函數。
活動對象:如果執行環境是函數,則將其活動對象作為變數對象。活動對象在最開始時只包含一個變數。
在函數執行過程中,為讀取和寫入變數的值,就需要的作用域鏈中查找對應的變數。先看看一個實例來瞭解下每個概念對應部分:
1 function compare(value1,value2){ 2 3 if(value1<value2){ 4 return -1; 5 }else if(value1>value2){ 6 return 1; 7 }else{ 8 return 0; 9 } 10 }
var result = compare(5,10);
先定義了 compare 函數,後又在全局作用域中調用,在這個compare 執行環境為函數的時候,當前活動對象則為變數對象。 活動對象預設是只有 arguments 但是此時卻不一樣(this,arguments,value1,value2);
而全局作用域中對應變數對象(compare 、result);來看看下圖吧!
<1>在定義compare函數的時候,會創建一個預先包含全局變數對象作用域鏈,這個作用域鏈保存在內部的[[Scope]]屬性中。
<2>當在調用 compare 函數的時候,會為函數創建一個執行環境,然後通過賦值函數的[[Scope]]屬性中的對象構建起執行環境的作用域鏈。此後,又有一個活動對象被創建並推入執行環節作用域鏈的前端。
對於compare函數的執行環境而言,其作用域鏈中包含兩個變數對象(本地活動對象和全局變數對象)。顯然,作用域鏈本質上是一個指向變數對象的指針列表,它引用但不實際包含變數對象。
所以無論什麼時候,訪問一個變數時都會從作用域鏈中搜索具有相應名字的變數。一般來講當函數執行完畢後。局部活動對象就會被銷毀,記憶體中僅保存全局作用域。但是閉包所有不同。
在閉包中,當函數內部定義一個內部函數,該內部函數會把外部函數的活動對象添加到自己的作用域鏈中;因此createComparisonFunction()定義的匿名函數,其作用於域會包含外部函數的作用域。
1 function createComparisonFunction(propertyName){ 2 return function(object1,object2){ 3 var value1 = object1[propertyName]; 4 var value2 = object2[propertyName]; 5 if(value1<value2){ 6 return -1; 7 }else if(value1>value2){ 8 return 1; 9 }else{ 10 return 0; 11 } 12 } 13 };
當下麵代碼執行時,外部函數與內部函數包含的作用域鏈, 如下圖
1 var compare = createComparisonFunction("name"); 2 var result = compare({name:"Nicholas"},{name:"Greg"});
在代碼執行過程中,用變數 compare 保存了其返回的內部匿名函數,而作用域鏈中包含了三個變數對象,(全局變數對象,外部函數-活動對象,匿名函數-活動對象);
所以即使返回後,也還是可以訪問到對應的變數; 因為外部函數(createComparisonFunction) 執行完成時,其活動對象依然存在,並不會銷毀,因為其被內部函數作用域鏈中引用了;
雖然 外部函數返回到,其執行環境對應作用域鏈被銷毀,但是其活動對象依然存在。直到匿名函數被銷毀,其createComparisonFunction對應的活動對象才會被銷毀。
1 var compare = createComparisonFunction("name"); 2 var result = compare({name:"Nicholas"},{name:"Greg"}); 3 compare = null; //主要是手動賦值為空,通知系統 進行垃圾處理; 隨著其作用域鏈被銷毀(除全局作用域鏈之外)。
由於閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的記憶體。過度使用閉包可能會導致內部占用過多。