文章內容是讀“JavaScript高級程序設計”的筆記。
函數其實也是一個對象,每個函數都是Function類型的一個實例,與其他引用類型一樣具有屬性和方法。由於函數是對象,因此函數名稱實際上是一個指向函數的指針。
一、定義函數的幾種方式:例如我們以返回兩個數值的和爲例。
- 函數聲明:這種方式定義的函數的作用域是全局的。
function sum (num1, num2) {
return num1 + num2;
}
- 函數表達式:這種方式聲明的函數與函數聲明的區別主要是作用域的差別,用這種方式定義,在調用函數時,必須要在定義函數所在的作用域內,而且必須要在函數被聲明之後才能調用,否則就會報錯,而函數聲明就可以在函數聲明之前調用。
var sum = function(num1, num2){
return num1 + num2;
};
- 創建Function類型實例:這種聲明方式可以很好的理解“函數即是對象,函數名即是指針”。這種聲明方式效率不高,因爲js引擎需要解析兩次代碼,一次是常規的解析,第二次是解析傳入構造函數中的字符串。
var sum = new Function("num1", "num2", "return num1 + num2");
無論是哪一種定義函數的方式,在調用函數時,都是函數名後面加上括號
()
,如果有參數則在括號內加上參數。
二、函數的其他特點
- 沒有重載方法:如果定義了兩個同名函數,前面的函數會被後面的覆蓋。
- 函數可以被當做一個變量來傳遞:函數本身也是一個變量,函數可以當做函數的參數,函數也可以當做函數的返回值。例如下面的例子:
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1);//20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2);//"Hello, Nicholas"
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;
}
};
}
三、函數的屬性
3.1 在函數內部,有兩個特殊的對象: arguments
和this
.
- 第一個屬性arguments 的主要用途是保存函數參數,它還有一個屬性叫做
callee
,該屬性是一個指針,指向擁有這個arguments對象的函數。這個屬性在遞歸函數中可以很好的解開函數的執行與函數名之間耦合。例如這個階乘函數:
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}
上面的例子中函數的執行與函數名 factorial 緊緊耦合在了一起。爲了解決這個問題,可以將函數修改成下面這樣。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)//將這一步原來使用函數名改爲callee屬性。
}
}
修改之後無論引用函數時使用的是什麼名字,都可以保證正常完成遞歸調用。
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5));//120
alert(factorial(5));//0
3.2 函數的另外兩個屬性length 和 prototype
length
屬性表示函數希望接收的命名參數的個數。- prototype 是保存它們所有實例方法的真正所在。換句話說,諸如toString() 和 valueOf() 等方法實際上都保存在 prototype 名下,只不過是通過各自對象的實例訪問罷了。
- prototype 屬性是不可枚舉的,因此使用 for-in 無法發現。
3.3 函數的兩個方法apply()
和call()
這兩個方法的用途都是在特定的作用域中調用函數,實際上等於
設置函數體內this對象的值
。
apply()
方法接收兩個參數:一個是在其中運行函數的作用域,另一個是參數數組。第二個參數可以是 Array 的實例,也可以是arguments 對象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments);// 傳入 arguments 對象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);// 傳入數組
}
alert(callSum1(10,10));//20
alert(callSum2(10,10));//20
call()
方法與apply()方法的作用相同,它們的區別僅在於接收參數的方式不同。call()方法第一個參數是 this 值沒有變化,變化的是其餘參數都直接傳遞給函數。換句話說,在使用call() 方法時,傳遞給函數的參數必須逐個列舉出來。例如:
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10));//20
3. apply()
和call()
正強大的地方是能夠擴充函數賴以運行的作用域。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
sayColor.call(o)
的意思是將sayColor函數在對象實例o的作用域中調用。