函數表達式是JavaScript當中一個既強大又使人困惑的特性,特別是其中涉及到的閉包,更是令許多的初學者困惑不已。前端
在以前的章節中有介紹過,定義函數的方法有兩種:一種是函數聲明。數組
function functionName(arg0, arg1, arg2) { //函數體 }
對於函數聲明,有一個重要的特徵就是能夠把函數聲明放在調用它的語句後面,由於解釋器會在執行語句以前先讀取函數的聲明。閉包
另外一種方法是使用函數表達式。函數
var functionName = function(arg0, arg1, arg2){ //函數體 };
函數表達式與函數聲明的一個主要不一樣就是,它必須在定義以後才能使用,不然將會報錯。函數表達式中的函數是一個匿名函數,即function
關鍵字後面沒有標識符。既然能夠把函數賦值給變量,也就能夠把函數做爲其它函數的返回值。this
JavaScript中的遞歸有個問題,將保存函數的變量賦值給另外一個變量時,由於函數名稱改變了,因此在遞歸調用的時候會出現問題:prototype
function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //出錯!
要解決這個問題,可使用arguments.callee
。這個屬性是一個指向正在執行的函數的指針,因此能夠利用它來代替函數名,這就確保遞歸調用時函數名稱改變也不會出錯。可是,在嚴格模式下,使用argments.callee
會致使錯誤。咱們可使用命名函數來達成相同的結果:指針
var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } });
閉包是一個容易使人困惑的概念。閉包是指有權訪問另外一個函數做用域中的變量的函數。建立裝飾的常見方式就是嵌套定義函數。code
咱們在第4章的時候瞭解過做用域鏈。而對於做用域鏈清晰地理解,是理解閉包的重要關鍵。對象
當調用函數的時候,會爲函數建立一個執行環境,並將該環境的活動對象加入到做用域鏈的前端。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。遞歸
當在一個函數內部定義另外一個函數的時候,外部函數的活動對象會被添加到內部函數的做用域鏈中。通常狀況下,當函數執行完畢後,它的活動對象就會被銷燬。可是,當有內部函數與其構成閉包時,由於內部函數的做用域鏈仍在引用外部函數的活動對象,所以只有當內部的函數也被銷燬後,這個外部活動對象纔會被銷燬。
閉包只能取得包含函數(即外部函數)中任何變量的最後一個值。
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; }
這個函數返回的函數數組中的每一個函數都會返回10。由於每一個函數的做用域鏈中都保存着createFunctions()
的活動對象,因此它們引用的都是同一個變量i
。當createFunctions()
返回後,i
的值是10,因此每一個函數引用保存的變量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; }
this
對象是在運行時,基於函數的執行環境綁定的。可是,匿名函數的執行環境具備全局性,所以其this
對象一般指向window
。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)
這是由於每一個函數都有本身的this
,因此當內部函數在搜索這個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問到外部函數中的this
。
以前講過,在JavaScript當中沒有塊級做用域。可是,咱們可使用匿名函數來模仿塊級做用域。
(function(){ // 這裏是塊級做用域 })();
在函數的聲明包含在一對圓括號中,表示它其實是一個函數表達式,而緊隨其後的另外一對圓括號會當即調用這個函數。
在須要用到塊級做用域的時候,就能夠這樣使用:
function outputNumbers(count){ (function () { for (var i=0; i < count; i++){ alert(i); } })(); alert(i); //致使一個錯誤! }
從技術角度來說,JavsScript中是沒有私有成員的概念的,全部對象屬性都是公開的。因可是對於函數而言,函數裏面的變量對外部都是私有的,咱們能夠利用在函數裏面建立一個閉包,來建立用於訪問函數內部私有變量的公有方法。
對於有權訪問私有變量和私有函數的公有方法,咱們稱之爲特權方法(privileged method)。建立特權方法的方式有兩種:一是在構造函數當中定義特權方法。
function MyObject(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //特權方法 this.publicMethod = function (){ privateVariable++; return privateFunction(); }; }
對於這個例子而言,變量privateVariable
和函數privateFunction()
只能經過特權方法publicMethod()
來訪問,咱們沒法直接訪問到內部私有的變量。
使用模式的缺點是針對每一個實例都會建立一樣一組新方法,可使用另外一種方法,靜態私有變量來避免這個問題。
經過在私有做用域中定義私有變量和函數,也能夠建立特權方法:
(function(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //構造函數 MyObject = function(){ }; //公有/特權方法 MyObject.prototype.publicMethod = function(){ privateVariable++; return privateFunction(); }; })();
這個模式使用了函數表達式來定義特權方法,由於函數聲明只能建立局部的函數,一樣地,對於MyObject
咱們也沒有使用var
關鍵字,這樣可使其成爲一個全局變量。
這個模式與構造函數模式的主要區別在於,私有變量和函數是由實例共享的。因爲特權方法是在原型上定義的,所以全部實例都使用同一個函數。而這個特權方法做爲一個裝飾,問題保存着對包含做用域的引用。
模塊模式是爲單例建立私有變量和特權的方法。所謂單例,即只有一個實例的對象。
通常狀況下,JavaScript是以對象字面量的方式來建立單例對象的。
模塊模式經過爲單例添加私有變量和特權方法可以使其獲得加強,其語法形式以下:
var singleton = function(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //特權/公有方法和屬性 return { publicProperty: true, publicMethod : function(){ privateVariable++; return privateFunction(); } }; }();
若是必須建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。
這個模式即在返回對象以前加入對其加強的代碼。這種加強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性或方法對其加以增長的狀況。
var singleton = function(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //建立對象 var object = new CustomType(); //添加特權/公有屬性和方法 object.publicProperty = true; object.publicMethod = function(){ privateVariable++; return privateFunction(); }; //返回這個對象 return object; }();
這章主要討論了JavaScript當中的函數表達式與閉包。理解閉包的一個重要基礎就是要透徹理解執行環境和做用域鏈。
函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。不過,由於建立閉包必須維護額外的做用域,因此過分使用它們可能會佔用大量內存。