定義函數的方式有兩種:一種是函數聲明,一種就是函數表達式。javascript
關於函數聲明,它的重要特徵:函數聲明提高(function declaration hosting)html
<!DOCTYPE html> <html> <head> <title>Function Name Example</title> </head> <body> <script type="text/javascript"> //函數聲明 function functionName(){ //noop } //works only in Firefox, Safari, Chrome, and Opera alert(functionName.name); //"functionName" //函數聲明提高 sayHi(); function sayHi(){ alert("Hi!"); } var fname=function(arg0,arg1){ //函數體 這種狀況下建立的函數叫作匿名函數(有時候也叫拉姆達函數),由於function後面沒有標識符。匿名函數的name屬性是空字符串。 } </script> </body> </html>
如下代碼在ECMAScript中屬於無效語法,若是是使用函數表達式就沒有問題。java
<!DOCTYPE html> <html> <head> <title>Function Declaration Error Example</title> </head> <body> <script type="text/javascript"> var condition = true; //never do this! if(condition){ function sayHi(){ alert("Hi!"); } } else { function sayHi(){ alert("Yo!"); } } sayHi(); </script> </body> </html>
把函數當成值來使用的狀況下,均可以使用匿名函數。c#
7.1 遞歸 數組
經典的遞歸階乘函數,雖然表面上看沒有問題,但下面的代碼可能致使它出錯。閉包
<!DOCTYPE html> <html> <head> <title>Recursion Example</title> </head> <body> <script type="text/javascript"> function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //error! </script> </body> </html>
在嚴格模式下不能經過腳本訪問 arguments.callee,訪問這個屬性會致使錯誤。app
<!DOCTYPE html> <html> <head> <title>Recursion Example</title> </head> <body> <script type="text/javascript"> function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //24 </script> </body> </html>
以下方式在嚴格模式和非嚴格模式都行得通函數
<!DOCTYPE html> <html> <head> <title>Recursion Example</title> </head> <body> <script type="text/javascript"> factorial=(function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } }) var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //24 </script> </body> </html>
7.2 閉包oop
如何建立做用域鏈以及做用域鏈有什麼做用的細節,對完全理解閉包相當重要。當某個函數第一次被調用時,會建立一個執行環境(execution context)及相應的做用域鏈,並把做用域鏈賦值給一個特殊的內部屬性(即[[Scope]])。而後,使用this、argument和其它命名參數的值來初始化函數的活動對象(activetion object)。但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象處於第三位,......直至做爲做用域鏈終點的全局執行環境。後臺的每一個執行環境都有一個表示變量的對象---變量對象。全局的變量對象始終存在。而局部環境的變量對象,則只在函數執行的過程當中存在。做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。this
閉包是指有權訪問另外一個函數做用域中的變量的函數(函數調用返回後一個沒有釋放資源的棧區),建立閉包的常見方式,就是在一個函數內部建立另外一個函數。
因爲閉包會攜帶包含它的函數的做用域,所以比其餘函數佔用更多的內存。建議只在絕對必要時考慮使用閉包。
閉包的做用:一是能夠讀取函數內部的變量;二是讓這些變量的值始終保持在內存中。
7.2.1 閉包與變量
做用域鏈的這種配置機制引出了一個值得注意的反作用,即閉包只能取得包含函數中任何變量的最後一個值。別忘了閉包所保存的是整個變量對象,而不是某個特殊的變量,下例能夠清淅的說明問題:
<!DOCTYPE html> <html> <head> <title>Closure Example</title> </head> <body> <script type="text/javascript"> function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; } var funcs = createFunctions();//此時變量i的值是10 //every function outputs 10 for (var i=0; i < funcs.length; i++){ document.write(funcs[i]() + "<br />"); } </script> 表面上看好像每一個函數都應該返本身的索引值,但實際上每一個函數都返回10,由於第個函數的做用域鏈中都保存着createFunctions()函數的活動對象,因此它們都引用的同一個變量i,當createFunctions()函數返回後,變量i的值是10,此時每一個函數都引用着保存變量i的同一個變量對象,因此每一個函數的內部i的值都是10。
</body> </html>
<!DOCTYPE html> <html> <head> <title>Closure Example 2</title> </head> <body> <script type="text/javascript"> function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(num){ return function(){ return num; }; }(i);//這裏的匿名函數有一個參數num,也就是最終的函數要返回的值。在調用每一個匿名函數時,咱們傳入了變量i,因爲函數參數是按值傳遞的,因此就會將變量i的當前值複製給參數num。而這個匿名函數內部又建立和並返回了一個訪問num的閉包。這樣一來result數組中的每一個函數都有本身num變量的一個副本,所以就能夠返回各自不一樣的數值了。 } return result; } var funcs = createFunctions(); //every function outputs 10 for (var i=0; i < funcs.length; i++){ document.write(funcs[i]() + "<br />"); } </script>
</body> </html>
7.2.2 關於this對象
每一個函數在被調用時,其活動對象都會自動取得兩個特殊變量:this和arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。arguments和this同樣,也存在一樣的問題。若是想訪問做用域中的this(arguments)對象,必須將對該對象的引用保存到另外一個閉包可以訪問的對象裏。匿名函數的執行環境具備全局性,所以其this對象一般指向window。
<!DOCTYPE html> <html> <head> <title>This Object Example</title> </head> <body> <script type="text/javascript"> var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window" </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>This Object Example 2</title> </head> <body> <script type="text/javascript"> var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this;// 把外部做用域中的this對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。 return function(){ return that.name; }; } }; alert(object.getNameFunc()()); //"MyObject" </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>This Object Example 3</title> </head> <body> <script type="text/javascript"> var name = "The Window"; var object = { name : "My Object", getName: function(){ return this.name; } }; alert(object.getName()); //"My Object" alert((object.getName)()); //"My Object" alert((object.getName = object.getName)()); //"The Window" in non-strict mode </script> </body> </html>
7.3 模仿塊級做用域
用做塊級做用域(一般稱爲私有做用域)的匿名函數的語法以下所示(這種技術常常在全局做用域中被用在函數外部,從而限制在全局做用域中添加過多的變量和函數):
(fuction(){
//這裏是塊級做用域
})()
定義並當即調用了一個匿名函數
fuction(){
//這裏是塊級做用域
}()//出錯!致使語法錯誤的緣由是由於javascript把fuction看成函數聲明的開始,而函數聲明後面不能跟圓括號。
不管在什麼地方,只要臨時須要一些變量,就可使用私有做用域,例如:
<!DOCTYPE html> <html> <head> <title>Block Scope Example</title> </head> <body> <script type="text/javascript"> function outputNumbers(count){ (function () { for (var i=0; i < count; i++){ alert(i); } })(); alert(i); //causes an error 在匿名函數中定義的任何變量,都會在函數執行結束時被銷燬 } outputNumbers(5); </script> </body> </html>
通常來講咱們應該儘可能少向全局做用域中添加變量和函數,在一個由不少開發人員共同參與的大型應用程序中,過多的全局變量和函數很容易致使命名衝突。而經過建立私有做用域,開發人員既可使用本身的變量,又不怕搞亂全局做用域。
(function(){
var now=new Date();
if(now.getMonth()==0&&now.getDate==1){
alert("happy new year!");
}
})();
這種做法能夠減小閉包占用的內存問題,由於沒有指向匿名函數的引用。只要函數執行完畢,就能夠當即銷燬其做用域鏈了。
7.4 私有變量
嚴格來說,javascript中沒有私有成員的概念,全部對象屬性都是公有的,不過卻是有一個私有變量的概念。私有變量包括:函數的參數、局部變量、在函數內部定義的其它函數(構造函數裏定義的function,即爲私有方法)。利用閉包能夠建立用於訪問私有變量的公有方法,把有權訪問私有變量和私有函數的公有方法稱爲特權方法,有兩種建立特權方法的方式:
var Person = function(name,sex){ this.name = name; this.sex = sex; var _privateVariable = "";//私有變量 //構造器中定義的方法,即爲私有方法 function privateMethod(){ _privateVariable = "private value"; alert("私有方法被調用!私有成員值:" + _privateVariable); } privateMethod(); //構造器內部能夠調用私有方法 } Person.prototype.sayHello = function(){ alert("姓名:" + this.name + ",性別:" + this.sex); } var p = new Person("Nicholas","男");
p.sayHello();
//p.privateMethod();//這裏將報錯,私成方法沒法被實例調用
alert(p._privateVariable);//顯示: undefined
說明:類的構造函數裏定義的function,即爲私有方法;而在構造函數裏用var聲明的變量,也至關因而私有變量。(不過類比於c#這類強類型語言中的私有成員概念仍是有區別的,好比沒法在非構造函數之外的其它方法中調用)
相似的,咱們還能實現相似set,get屬性的封裝
<!DOCTYPE html> <html> <head> <title>Privileged Method Example</title> </head> <body> <script type="text/javascript"> function Person(name){ this.getName = function(){ return name; }; this.setName = function (value) { name = value; }; } var person = new Person("Nicholas"); alert(person.getName()); //"Nicholas" person.setName("Greg"); alert(person.getName()); //"Greg" </script> </body> </html>
在構造函數中定義特權方法也有一個缺點,那就是必須使用構造函數模式來達到這個目的。使用靜態私有變量特權方法就能解決這個問題。
7.4.1 靜態私有變量
<!DOCTYPE html> <html> <head> <title>Privileged Method Example 2</title> </head> <body> <script type="text/javascript"> (function(){ var name = ""; Person = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; Person.prototype.setName = function (value){ name = value; }; })(); var person1 = new Person("Nicholas"); alert(person1.getName()); //"Nicholas" person1.setName("Greg"); alert(person1.getName()); //"Greg" var person2 = new Person("Michael"); alert(person1.getName()); //"Michael" alert(person2.getName()); //"Michael" </script> </body> </html>
這個例子裏name變成了一個靜態的、由全部實例共享的屬性。以這種方式建立靜態私有變量會由於使用原型增進代碼利用,但每一個實例都沒有本身的私有變量。究竟是使用實例變量,仍是靜態私有變量,最終視具體需求而定。
7.4.2 模塊模式
前面的模式是用於爲自定義類型建立私有變量和特權方法的。模塊模式則是爲單例建立私有變量和特權方法。所謂單例,就是隻有一個實例的對象。
<!DOCTYPE html> <html> <head> <title>Module Pattern Example</title> </head> <body> <script type="text/javascript"> function BaseComponent(){ } function OtherComponent(){ } var application = function(){ //private variables and functions var components = new Array(); //initialization components.push(new BaseComponent()); //public interface return { getComponentCount : function(){ return components.length; },//返回已註冊的組件數目 registerComponent : function(component){ if (typeof component == "object"){ components.push(component); } }//後者用於註冊新組件 }; }(); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2 </script> </body> </html>
在Web應用程序中,常常須要使用一個單例來管理應用程序級的信息。這個簡單的例子建立了一個用於管理組件的application對象。簡言之,若是必需要建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。以這種模式建立的每一個單例都是Object的實例,由於最終要經過一個對象字面量來表示它。事實上這也沒什麼,畢竟單例一般都是做爲全局對象存在的,咱們不會將它傳遞給一個函數,所以也沒有必要用instansof操做符來檢查它的對象類型。
7.4.3 加強的模塊模式
<!DOCTYPE html> <html> <head> <title>Module Pattern Example</title> </head> <body> <script type="text/javascript"> function BaseComponent(){ } function OtherComponent(){ } var application = function(){ //private variables and functions var components = new Array(); //initialization components.push(new BaseComponent()); //create a local copy of application var app = new BaseComponent(); //public interface app.getComponentCount = function(){ return components.length; }; app.registerComponent = function(component){ if (typeof component == "object"){ components.push(component); } }; //return it return app; }(); alert(application instanceof BaseComponent); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2 </script> </body> </html>
這種模式適合那些單例必須是某種類型的實例,同時必須添加某些屬性和(或)方法對其加以加強。
7.5 小結
javascript的函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。