在JavaScript中,函數其實是對象,每個函數都是Function類的實例,既然函數對象,那么就具有自己的屬性和方法,因此,函數名實際上也是一個指向函數對象的指針,不會與某個函數綁定。
一、函數的聲明
方式一:常規方式
1 function sum1(num1,num2){ 2 return num1+num2 3 }
方式二:函數表達式
1 var sum2=function(num1,num2){ 2 return num1+num2; 3 };
方式三:動態創建函數(這種方式用得不多)
1 var sum3=new Function("num1","num2","return num1+num2");//動態創建函數
測試:
1 <script type="text/javascript"> 2 function sum1(num1,num2){ 3 return num1+num2 4 } 5 var sum2=function(num1,num2){ 6 return num1+num2; 7 }; 8 var sum3=new Function("num1","num2","return num1+num2"); 9 //測試 10 document.write("<pre>"); 11 document.writeln("sum1(1,2)="+sum1(1,2)); 12 document.writeln("sum2(1,2)="+sum2(1,2)); 13 document.writeln("sum3(1,2)="+sum3(1,2)); 14 document.write("</pre>"); 15 </script>
測試結果:
這里順便提一下方式一和方式二這兩種函數聲明方式的區別,對於采用方式一聲明的函數,解析器會率先讀取函數聲明,並使其置於任何代碼之前;而對於函數表達式,則必須等到解析器執行到它所在的代碼行,才別執行。
可以用如下的代碼證明:
1 alert("sum1(1,2)="+sum1(1,2));//可以正常運行 2 function sum1(num1,num2){ 3 return num1+num2 4 } 5 alert("sum2(1,2)="+sum2(1,2));//錯誤:缺少對象 6 var sum2=function(num1,num2){ 7 return num1+num2; 8 };
二、函數重載
JavaScript沒有方法重載的說法,如果兩個方法名字一樣,即使參數個數不一樣,那么后面定義的就會覆蓋前面定義,調用方法時永遠是調用后定義的那個。
用如下的代碼證明JavaScript不支持函數的重載:
1 <script type="text/javascript"> 2 function Test(a){ 3 alert(a); 4 } 5 function Test(a,b){ 6 alert("HelloWorld!"); 7 } 8 function Test(a,b){ 9 alert(a+" "+b); 10 } 11 12 Test(20);//調用的是最后定義的那個Test方法 13 Test(30,50);//調用的是最后定義的那個Test方法 14 </script>
把函數名想像成指針,這例子中聲明了三個同名函數,最后聲明的一個就會覆蓋了前面函數,用下面的寫法或許更好理解:
1 /*聲明一個變量Test,Test變量中存放了指向函數對象的指針(引用)*/ 2 var Test =function (a){ 3 alert(a); 4 } 5 6 /*將變量Test指向了另一個函數對象*/ 7 Test = function (a,b){ 8 alert("HelloWorld!"); 9 } 10 /*將變量Test指向了另一個函數對象*/ 11 Test = function (a,b){ 12 alert(a+" "+b); 13 }
三、JavaScript函數的特殊性
JavaScript中的函數名本身就是變量,所以函數也可以當作普通變量來使用。也就是說,不僅可以像傳遞參數一樣把一個函數傳遞給另一個函數,而且可以將一個函數作為另一個函數的結果返回。
例如:在使用構造函數給一個對象初始化屬性時,指定函數屬性
1 <script type="text/javascript"> 2 function fn(num1,num2,oper){ 3 var result=-1; 4 switch(oper){ 5 case "+": 6 result=num1+num2; 7 break; 8 case "-": 9 result=num1-num2; 10 break; 11 case "*": 12 result=num1*num2; 13 break; 14 case "/": 15 result=num1/num2; 16 break; 17 } 18 return result; 19 } 20 /*創建Person類*/ 21 function Person(name,age,fn){ 22 this.Name=name; 23 this.Age=age; 24 this.Fn=fn;//使用傳遞進來的fn函數為函數屬性Fn初始化 25 } 26 /*在使用構造函數給一個對象初始化屬性時,也可以指定函數屬性*/ 27 var p1 = new Person("孤傲蒼狼",24,fn); 28 var retVal=p1.Fn(1,2,"+"); 29 alert(retVal); 30 </script>
在函數內部,有兩個特殊的對象:arguments和this,arguments是一個數組對象,包含傳入的所有參數,arguments的主要作用是保存函數參數,但這個對象還有個叫callee的屬性,該屬性是一個指向擁有這個arguments對象的函數。
1 //非常經典的遞歸函數 2 function factoriak(num){ 3 if(num<=1){ 4 return 1; 5 }else{ 6 return num * facaorial(num-1);//與函數名factoriak耦合性太高了 7 } 8 } 9 //上述代碼與函數名耦合性太高,一換函數名就不行了,就可以采用以下方法 10 function factoriak(num){ 11 if(num<=1){ 12 return 1; 13 }else{ 14 return num * arguments.callee(num-1);//這樣無論用什么名字都能完成遞歸調用 15 } 16 }
this指的是函數執行時所處的作用域
每個函數都包含:length和prototype。length屬性表示函數希望接收的參數個數。
1 function sayName(name){ 2 alert(name); 3 } 4 function sum(num1,num2){ 5 return num1+num2; 6 } 7 function sayHi(){ 8 alert("hi"); 9 } 10 alert("sayName函數接收的參數個數是:"+sayName.length);//1 11 alert("sum函數接收的參數個數是:"+sum.length); //2 12 alert("sayHi函數接收的參數個數是:"+sayHi.length);//0
每個函數都包含兩個非繼承而來的方法:apply()和call();這兩個方法的用途都是在特定的作用域中調用函數,實際上等於設置函數體內this對象的值
apply()方法接收兩個參數:一個是在其中運行函數的作用域,另一個是參數數組。其中第二個參數可以是Array的實例,也可以是arguments對象。
1 function sum(num1,num2){ 2 return num1+num2; 3 } 4 function callSum(num1,num2){ 5 return sum.apply(this,arguments); //傳入aguments對象 6 } 7 function callSum2(num1,num2){ 8 return sum.apply(this,[num1,num2]); //傳入數組 9 } 10 alert(callSum(10,10)); //20 11 alert(callSum2(10,10)); //20
call()方法接第一個參數是函數運行的作用域,其余的參數就是傳遞給函數運行時需要的參數(一個或多個)。
function sum(num1,num2){ return num1+num2; } function callSum(num1,num2){ return sum.call(this,num1,num2); } alert(callSum(10,10));
apply()和call()最強大的地方是能夠擴充函數賴以運行的作用域
1 window.color="red"; 2 var o ={color:"blue"}; 3 function sayColor(){ 4 alert(this.color); 5 } 6 sayColor(); //red 7 sayColor.call(this); //red 8 sayColor.call(window); //red 9 sayColor.call(o); //blue