函數是進行模組化程式設計的基礎,編寫複雜的Ajax應用程序,必須對函數有更深入的了解。
javascript中的函數不同於其他的語言,每個函數都是作為一個物件被維護和運行的。透過函數物件的性質,可以很方便的將一個函數賦值給一個變數或將函數傳遞為參數。在繼續講述之前,先看一下函數的使用語法:
以下是引用片段:
function func1(…){…} var func2=function(…){…}; var func3=function func4(…){…}; var func5=new Function();
這些都是聲明函數的正確語法。它們和其他語言中常見的函數或先前介紹的函數定義方式有著很大的差異。那麼在JavaScript中為什麼可以這麼寫呢?它所遵循的語法是什麼呢?下面將介紹這些內容。
認識函數物件(Function Object)
可以用function關鍵字定義一個函數,並為每個函數指定一個函數名,透過函數名稱來呼叫。當JavaScript解釋執行時,函數都是被維護為一個對象,這就是要介紹的函數物件(Function Object)。
函數物件與其他使用者所定義的物件有著本質上的區別,這一類物件稱為內部對象,例如日期物件(Date)、陣列物件(Array)、字串物件(String)都屬於內部物件。這些內建物件的建構器是由JavaScript本身定義的:透過執行new Array()這樣的語句傳回一個對象,JavaScript內部有一套機制來初始化傳回的對象,而不是由使用者來指定物件的建構方式。
在JavaScript中,函數物件對應的類型是Function,正如陣列物件對應的類型是Array,日期物件對應的類型是Date一樣,可以透過new Function()來建立一個函數對象,也可以透過function關鍵字來創建一個物件。為了便於理解,我們比較函數物件的創建和數組物件的創建。先看陣列物件:下面兩行程式碼都是建立一個陣列物件myArray:
以下是引用片段:
var myArray=[]; //等价于 var myArray=new Array();
同樣,下面的兩段程式碼也都是建立一個函數myFunction:
function myFunction(a,b){ return a+b; } //等价于 var myFunction=new Function("a","b","return a+b");
通過和建構陣列物件語句的比較,可以清楚的看到函數對象本質,前面介紹的函數聲明是上述代碼的第一種方式,而在解釋器內部,當遇到這種語法時,就會自動構造一個Function對象,將函數作為一個內部的物件來儲存和運行。從這裡也可以看到,一個函數物件名稱(函數變數)和一個普通變數名稱具有相同的規範,都可以透過變數名稱來引用這個變量,但是函數變數名稱後面可以跟上括號和參數列表來進行函數調用。
用new Function()的形式來建立一個函數不常見,因為一個函數體通常會有多條語句,如果將它們以一個字串的形式作為參數傳遞,程式碼的可讀性差。以下介紹其使用語法:
以下是引用片段:
var funcName=new Function(p1,p2,...,pn,body);
參數的類型都是字串,p1到pn表示所創建函數的參數名稱列表,body表示所創建函數的函數體語句,funcName就是所建立函數的名稱。可以不指定任何參數來建立一個空函數,不指定funcName建立一個無名函數,當然那樣的函數沒有任何意義。
需要注意的是,p1到pn是參數名稱的列表,即p1不僅能代表一個參數,它也可以是一個逗號隔開的參數列表,例如下面的定義是等價的:
以下是引用片段:
new Function("a", "b", "c", "return a+b+c") new Function("a, b, c", "return a+b+c") new Function("a,b", "c", "return a+b+c")
JavaScript引入Function類型並提供new Function()這樣的語法是因為函數物件添加屬性和方法就必須藉助於Function這個類型。
函數的本質是一個內部對象,由JavaScript解釋器決定其運作方式。透過上述程式碼所建立的函數,在程式中可以使用函數名進行呼叫。本節開頭列出的函數定義問題也得到了解釋。注意可直接在函數宣告後面加上括號就表示建立完成後立即進行函數調用,例如:
以下是引用片段:
var i=function (a,b){ return a+b; }(1,2); alert(i);
這段程式碼會顯示變數i的值等於3。 i是表示傳回的值,而不是建立的函數,因為括號「(」比等號「=」有更高的優先權。這樣的程式碼可能不常用,但當使用者想在很長的程式碼段中進行模組化設計或想避免命名衝突,這是一個不錯的解決辦法。一種方式創建的是有名函數,而後面是創建了一個無名函數,只是讓一個變數指向了這個無名函數。 ;而對於無名函數,它必須是在呼叫之前就已經定義。
<script language="JavaScript" type="text/javascript"> <!-- func(); function func(){ alert(1) } //--> </script>
则能够正确执行,下面的语句也能正确执行:
以下是引用片段:
<script language="JavaScript" type="text/javascript"> <!-- func(); var someFunc=function func(){ alert(1) } //--> </script>
由此可见,尽管JavaScript是一门解释型的语言,但它会在函数调用时,检查整个代码中是否存在相应的函数定义,这个函数名只有是通过function funcName()形式定义的才会有效,而不能是匿名函数。
函数对象和其他内部对象的关系
除了函数对象,还有很多内部对象,比如:Object、Array、Date、RegExp、Math、Error。这些名称实际上表示一个类型,可以通过new操作符返回一个对象。然而函数对象和其他对象不同,当用typeof得到一个函数对象的类型时,它仍然会返回字符串“function”,而typeof一个数组对象或其他的对象时,它会返回字符串“object”。下面的代码示例了typeof不同类型的情况:
以下是引用片段:
alert(typeof(Function))); alert(typeof(new Function())); alert(typeof(Array)); alert(typeof(Object)); alert(typeof(new Array())); alert(typeof(new Date())); alert(typeof(new Object()));
运行这段代码可以发现:前面4条语句都会显示“function”,而后面3条语句则显示“object”,可见new一个function实际上是返回一个函数。这与其他的对象有很大的不同。其他的类型Array、Object等都会通过new操作符返回一个普通对象。尽管函数本身也是一个对象,但它与普通的对象还是有区别的,因为它同时也是对象构造器,也就是说,可以new一个函数来返回一个对象,这在前面已经介绍。所有typeof返回“function”的对象都是函数对象。也称这样的对象为构造器(constructor),因而,所有的构造器都是对象,但不是所有的对象都是构造器。
既然函数本身也是一个对象,它们的类型是function,联想到C++、Java等面向对象语言的类定义,可以猜测到Function类型的作用所在,那就是可以给函数对象本身定义一些方法和属性,借助于函数的prototype对象,可以很方便地修改和扩充Function类型的定义,例如下面扩展了函数类型Function,为其增加了method1方法,作用是弹出对话框显示"function":
以下是引用片段:
Functiothod1=function(){ alert("function"); } function func1(a,b,c){ return a+b+c; } func1.method1(); functhod1();
注意最后一个语句:funchotd1(),它调用了method1这个函数对象的method1方法。虽然看上去有点容易混淆,但仔细观察一下语法还是很明确的:这是一个递归的定义。因为method1本身也是一个函数,所以它同样具有函数对象的属性和方法,所有对Function类型的方法扩充都具有这样的递归性质。