定義函數的方式有兩種:一種是函數聲明,另一種就是函數表達式。函數聲明的語法如下:
function functionName(arg0,arg1,arg2){//函數體 }
函數聲明,有一個重要特征就是函數聲明提升。也就是在執行代碼之前會先讀取函數聲明,也就意味著可以把函數聲明放在調用它的語句后面。
sayHi(); function sayHi(){console.log("Hi!"); }
下面介紹函數表達式的語法:
var functionName = function(arg0,arg1,arg2){//函數體 }
這種看起來好像常規的變量賦值語句,就是創建一個函數并將它賦值給變量functionName,這樣的函數就是匿名函數,注意function關鍵字后面沒有標識符,匿名函數的name屬性是空字符串。
函數表達式和其他表達式一樣,在使用之前必須先賦值。
sayHi(); //錯誤:函數還不存在 var sayHi = function(){alert("Hi!"); };
一、閉包
閉包是指有權訪問另一個函數作用域中的變量的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數。栗如:
function createComparisonFunction(propertyName) {return function(object1, object2){var value1 = object1[propertyName];var value2 = object2[propertyName];if (value1 < value2){return -1;} else if (value1 > value2){return 1;} else {return 0;}}; }
在函數執行過程中,為讀取和寫入變量的值,需要在作用域鏈中查找變量,栗如:
function compare(value1, value2){if (value1 < value2){return -1;} else if (value1 > value2){return 1;} else {return 0;} } var result = compare(5, 10);
上面代碼先定義了compare()函數,然后又在全局作用域調用了它,當調用compare()時,會創建一個包含arguments、value1、value2的活動對象。全局執行環境的變量對象在compare()執行環境的作用域鏈中則處于第二位,如圖:
在另一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的作用域鏈中。因此,在createComparisonFunction()函數內部定義的匿名函數的作用域鏈中,實際上將會包含外部函數createComparisonFunction()的活動對象。
var compare = createComparisonFunction("name"); var result = compare({ name: "Nicholas" }, { name: "Greg" });
在匿名函數從createComparisonFunction()中被返回后,它的作用域鏈被初始化為包含createComparisonFunction()函數的活動對象和全局變量對象。這樣,匿名函數就可以訪問在createComparisonFunction()中定義的所有變量。更為重要的是,createComparisonFunction()函數在執行完畢后,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象。換句話說,當createComparisonFunction()函數返回后,其執行環境的作用域鏈會被銷毀,但它的活動對象仍然會留在內存中;直到匿名函數被銷毀后,createComparisonFunction()的活動對象才會被銷毀。
//創建函數 var compareNames = createComparisonFunction("name"); //調用函數 var result = compareNames({ name: "Nicholas" }, { name: "Greg" }); //解除對匿名函數的引用(以便釋放內存) compareNames = null;
首先,創建的比較函數被保存在變量compareNames中,通過設置compareNames為null解除該函數的引用,就等于通知垃圾回收機制將其清除。匿名函數的作用域鏈被銷毀,其他作用域(除了全局作用域)也都可以安全地銷毀了。
作用域鏈帶來了一個副作用,閉包只能取得包含函數中任何變量的最后一個值。閉包所保存的是整個變量對象,而不是某個特殊的變量。舉栗:
function createFunctions(){var result = new Array();for (var i=0; i < 10; i++){result[i] = function(){return i;};}return result; }
這個函數會返回一個函數數組,表面上看,似乎每個函數都有自己的索引值,即位置0的函數返回0,位置1的函數返回1,以此類推。但實際上,每個函數都返回10。因為每個函數的作用域鏈中湊保存著createFunctions()函數的活動對象,所以它們引用的都是同一個變量i。當createFunctions()函數返回后,變量i的值是10,此時每個函數都引用著保存變量i的同一個變量對象,所以在每個函數內部i的值都是10。
我們可以通過創建另一個匿名函數強制讓閉包的行為符合預期。
function createFunctions(){var result = new Array();for (var i=0; i < 10; i++){result[i] = function(num){return function(){return num;};}(i);}return result; }
重寫了createFunctions()函數后,每個函數就會返回各自不同的索引值了。在這里,我們沒有直接把閉包賦值給數組,而是定義了一個匿名函數,并立即執行該匿名函數的結果賦給數組。這里的匿名函數有一個參數num,也就是最終的函數要返回的值。
在調用每個匿名函數時,我們傳入了變量i。由于函數參數是按值傳遞的,所以就會將變量i的當前值復制給參數num。而在這個匿名函數內部,又創建并返回一個訪問num的閉包。這樣一來,result數組中的每個函數都有自己num變量的一個副本,因此就可以參會各自不同的數值了。
?
參考資料
《javascript高級程序設計(第3版)》第7章 函數表達式