JavaScript 提供了四個建立函式的方式:
Function declaration 和 function expression 是最常見的函式定義方式,它們看起來很像,但有些微的不同,將在這篇文章裡探討。
Arrow function 則是 ES6 的新功能,它能夠用更簡潔的語法建立函式。
Generator 後面我們會講到,現在只要知道它也是 ES6 的新增功能就好了。
Function constructor不常見,它能動態的從字串建立函式,但只能夠建立全域函式,這就無法利用 JavaScript 的重要特性—closure,所以非常少機會使用,我們也就不花費時間研究。
var sum = new Function('a', 'b', 'return a + b');
console.log(sum(2,3));
// 5
Function declaration 是最基本的函式定義方式,它的結構嚴謹,必須包含以下的部份:
function
關鍵字開頭()
和大括號{}
// 宣告一個全域函式
function outterFunction() {
// 宣告一個內部函式
function innerFunction() {
return "Hello from innerFunction!";
}
return innerFunction();
}
// 呼叫函式的方法
outterFunction();
使用巢狀函式時,要注意變數和函式名稱的範圍(scope),這一點將在後面的篇幅說明。
前一篇文章我們談論過函式具有 first-class object 的特性,讓函式如同其他 JavaScript 基礎資料類型(Number
, String
等)一樣擁有任意建立、指派、傳遞和回傳的特權。
我們可以指派數字和字串給變數,也可以將數字或字串當成函式的參數。
var foo = 3;
var bar = "bar";
func(5){
return 10;
};
這樣的寫法在計算機程式裡叫做「表達式(expression)」。Expression 的結果一定會產生一個值,3
和bar
和5
都是值。
我們也可以在相同的位置使用函式。
var foo = function(){};
func(function(){}) {
return function() {};
};
因此這種建立函式的方法就叫做 function expression,在 JavaScript 引擎預期會看到值的地方建立函式,該函式就會被判定成 expression(值),例如在運算子的右邊、函式的參數,或是函式的回傳值。
Function expression 和 function declaration 最大的差別是 function expression 可以不要有名稱,沒有名稱的函式又叫匿名函式(anonymous function)。
另一個差異是函式名稱「提升(hoisting)」的現象,這一點後面會有一篇文章專門說明。
如果 function expression 被指派給一個變數,可以使用該變數來進行呼叫。
var func = function() {};
func();
// Function expression 的函式名稱可有可無,
// 如果有名稱的話,就可以在函式內用該名稱存取函式本身。
// 但是仍然要以被指派的變數來呼叫,不能以函式名稱呼叫。
var bar = function foo() {
foo.parm = "This is a function parameter.";
console.log(foo.parm);
};
bar(); // "This is a function parameter."
foo(); // Error: foo is not defined
如果是當成其他函式的參數,則可以用參數名稱來呼叫。
var func = function(foo) {
foo();
};
func(function() {
console.log("Hello from another function!");
});
除了可以用被指派的變數名稱來呼叫 function expression 之外, 在括號左邊也可以是一段 function expression,如此一來就可以立即呼叫這段剛建好的函式,這種寫法叫做 immediately invoked function expression (IIFE),簡稱立即函式。
// 標準函式呼叫
var square = function(n) {
return n * n;
};
console.log(square(5));
// 25
// 立即呼叫的 function expression
var result = (function(n) {
return n * n;
})(5);
console.log(result);
// 25
包圍在 function expression 外的括號叫做 grouping operators,沒有這組括號的話,JavaScript 就會報錯,因為如果沒有括號的話,程式碼就會變成function(){}(5)
,JavaScript 就會把它當成是一個 function declaration,但是沒有在function
關鍵字後面看到函式名稱,於是會回傳錯誤。括號的作用是在告知 JavaScript 引擎「這裡是一段 function expression」。如同在寫數學運算式時,乘除會優先於加減,一旦加了括號就可以先進行括號內的運算。
2 + 3 * 5 = 17
(2 + 3) * 5 = 25
立即函式還有其他種寫法。像是把整個立即函式都包在括號內。
var square = (function(n){
return n * n;
}(5));
或是在函式前加上一元運算子,這是在告訴 JavaScript 引擎,它處理的是一個 expression。
+function(){ return 3 + 2; }();
-function(){ return 3 + 2; }();
!function(){ return 3 + 2; }();
~function(){ return 3 + 2; }();
立即函式只會執行一次,其他的程式碼無法引用。它的用處在當場計算出一個值來。還有因為 JavaScript 變數範圍的特性,讓立即函式內的變數名稱不會和其他程式碼相同名稱的變數衝突,同時讓 JavaScript 模擬出模組化的功能,這在後面的文章會講到。
Arrow function 是 ES6 新加入的 JavaScript 標準,能讓我們以更簡潔的方式建立函式,我們來看看它的規則。
// 一段 function expression
var saySorry = function(name) {
return "I'm sorry " + name + ", I'm afraid I can't do that.";
};
首先先省略function
關鍵字,改用箭頭符號=>
。
var saySorry = (name) => {
return "I'm sorry " + name + ", I'm afraid I can't do that.";
};
如果只有一個參數,括號也可以省略。
var saySorry = name => {
return "I'm sorry " + name + ", I'm afraid I can't do that.";
};
如果程式碼只有一行,而且是回傳值的話,可以同時省略大括號和return
。
var saySorry = name => "I'm sorry " + name + ", I'm afraid I can't do that.";
以上的寫法在 JavaScript 裡都是通過的,而且是同樣功能的函式。
如果函式沒有參數,那麼括號就要保留。
var saySorryToDave = () => "I'm sorry Dave, I'm afraid I can't do that.";
回傳值是物件的話,要用括號把物件包起來。
var fullName = (firstName, lastName) => ( {firstName: firstName, lastName: lastName} );
從以上寫法可以看出來,arrow function 其實就是 function expression 的簡化寫法。而 function declaration 在 ES6 中並沒有簡化的方式,所以還是要用function
開頭來宣告函式。