簡析JavaScript中的Function型別(一)——函式名是指標
說起來ECMAScript中什麼最有意思,用原書(《JavaScript高階程式設計》)作者的話說——莫過於函數了,有意思的根源在於函式實際上是物件。每個函式都是Function
型別的例項,而且都與其他引用型別一樣具有屬性和方法。由於函式是物件,因此函式名實際上也就是一個指向函式物件的指標,不會與某個函式繫結。
函式的定義方式有三種:
- 函式宣告
- 函式表示式
- 使用
Function
建構函式
函式宣告的方式是比較常見的一種,如下面例子所示:
function sum(num1, num2){
return num1 + num2;
}
這與下面使用函式表示式定義函式的方式幾乎相差無幾:
var sum = function(num1, num2){
return num1 + num2;
};
上面的函式表示式語法定義了變數sum
,並將其初始化為一個函式。有讀者可能會注意到,function
關鍵字後面沒有函式名,這是因為在使用函式表示式定義函式的時候,沒有必要使用函式名,通過變數sum
即可以引用函式。另外,還要注意函式未尾有個分號,就像宣告其它變數一樣。
使用Function
建構函式定義函式時,Function
建構函式可以接收任意數量的引數,但最後一個引數始終都被看作是函式體,而前面的引數則枚舉出了新函式的引數。來看下面的例子:
var sum = new Function('num1', 'num2', 'return num1 + num2');
從技術上講,這也是一個函式表示式。但是,我們不推薦使用這種方式定義函式,因為這種函式定義方式會導致解析兩次程式碼(第一次是解析常規ECMAScript程式碼,第二次是解析傳入建構函式中的字串),從而影響效能。不過,這種方式對於理解“函式是物件,函式名是指標”來說倒是非常直觀的。
由於函式名僅僅是指向函式的指標,因此函式名與包含物件指標的其他變數沒有什麼不同。換句話說,一個函式可能會有多個名字,如下例所示:
function sum(num1, num2){
return num1 + num2;
}
console.log(sum(10, 10));// 20
var anotherSum = sum;
console.log(anotherSum(10, 10));// 20
sum = null;
console.log(anotherSum(10, 10));// 20
上面的程式碼首先定義了一個名為sum
的函式,用於求兩個數的和。然後,又聲明瞭變數anotherSum
,賦值為sum
,此時anotherSum
和sum
就指向了同一個函式,因此anotherSum()
也正常返回了結果。即使切斷sum
與函式物件的引用關係,也不會影響anotherSum
。
函式名作為指標,也可以理解為什麼ECMAScript中沒有函式過載(函式名相同,引數列表不同)的概念。來看下面的示例:
function add(num){
return 100 + num;
}
function add(num, num2){
return 200 + num;
}
console.log(add(100));// 300
按照函式過載的概念,兩個add
函式的引數列表不同,當傳入一個引數時應該呼叫第一個add
,當傳入兩個引數時應該呼叫第二個add
。但如上例所示,即使傳入一個引數依然是呼叫第二個add
,結果為300
,這是為什麼呢?
答案在函式名是指標,所以第二個add
覆蓋了第一個add
,也許用函式表示式的寫法更容易理解:
var add = function(num){
return 100 + num;
};
var add = function(num, num2){
return 200 + num;
};
如上例所示,重新宣告add
會覆蓋第一個add
。而JavaScript中的函式呼叫會根據實際傳入的引數個數按順序匹配引數列表,比如這裡傳入了一個100
,那麼add
中的num
就為100
,num2
為undefined
,如果像這樣呼叫add(100, 20)
,那麼num2
則為20
。
ECMAScript 2015引入了let
關鍵字可以避免變數覆蓋的問題,如下所示:
var add = function(num){
return 100 + num;
};
//Uncaught SyntaxError: Identifier 'add' has already been declared
let add = function(num, num2){
return 200 + num;
};
解析的時候已經報錯,提示add
已經宣告,所以使用let