不可輕忽的函式基礎


對於要重複執行的內容,你可以使用 function 定義函式,這在先前的文章中都看過一些例子,例如:

function max(num1, num2) {
    return num1 > num2 ? num1 : num2;
}

console.log(max(10, 20));  // 20

在上面的例子示範了函式的基本宣告與呼叫方式。函式使用 function 宣告名稱,參數位於括號之中,使用 return 傳回執行結果,如果沒有宣告 return 傳回任何結果,預設傳回 undefined

在函式上的參數宣告,只是傳入引數的具名參考,實際上,宣告函式時若傳入的引數少於參數是可行的,不足的部份,參數就是 undefined

function func(a, b) {
    console.log(a);
    console.log(b);
}

func(10, 20);         // 10 20
func(10);             // 10 undefined
func();               // undefined undefined
func(10, 20, 30, 40); // 10 20

在上例中,你也看到了,就算傳入比參數個數還多的引數也是可行的,那多的引數跑到哪去了?事實上,在函式內部會自動宣告 arguments 名稱參考至具陣列外觀的物件,上頭帶有所有傳入的引數。例如,你可以如下設計一個加總數值的函式:

function sum() {
    var sum = 0;    
    for(var i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}

console.log(sum(1, 2));;      // 3
console.log(sum(1, 2, 3));    // 6
console.log(sum(1, 2, 3, 4)); // 10

arguments 不是 Array 實例,它只是具有數字作為特性,特性參考至傳入的引數,並具有 length 特性代表傳入引數的個數。

若採用 EMCAScript 5 嚴格模式,參數的值與 arguments 的元素值彼此互不影響,例如:

'use strict'

function func(a, b) {
    console.log(a + ': ' + arguments[0]);  // 10: 10
    console.log(b + ': ' + arguments[1]);  // 20: 20
    a = 0;
    b = 0;
    console.log(a + ': ' + arguments[0]);  // 0: 10
    console.log(b + ': ' + arguments[1]);  // 0: 20
    arguments[0] = 100;
    arguments[1] = 200;
    console.log(a + ': ' + arguments[0]);  // 0: 100
    console.log(b + ': ' + arguments[1]);  // 0: 200
}

func(10, 20);

當然,改變參數值本身也不是個好的實踐,應該避免!ECMAScript 5 的嚴格模式下,也不允許重複的參數名稱。例如,以下將會發生 SyntaxError

'use strict'

function func(a, a, b) { // 會發生 SyntaxError
    // 做些事 ...
}

由於呼叫函式時傳入的引數個數不一定要等於參數個數,因此若要確認引數等於參數個數,可透過 argumentslength 來檢查傳入的引數個數。例如:

function plus(a, b) {
    if(arguments.length != 2) {
        throw new Error('必須有兩個引數');
    }
    return a + b;
}

console.log(plus(10, 20)); // 30
console.log(plus(10));     // Error: 必須有兩個引數

事實上,在 JavaScript 程式設計上的慣例,很少檢查引數個數,而是在引數不足時提供預設值,這很容易辦到,因為引數不足時,不足的參數會是 undefined,而 undefined 在判斷式中會被當 false,所以可以撰寫如下來提供預設值:

function rangeClosed(startInclusive, endInclusive, step) {
    var s = step || 1;
    var numbers = [startInclusive];
    for(var i = 0; numbers[i] < endInclusive; i++) {
        numbers.push(numbers[i] + s);
    }
    return numbers;
}

rangeClosed(1, 5, 2).forEach(function(elem) { // 1 3 5
    console.log(elem);
});

rangeClosed(1, 5).forEach(function(elem) {    // 1 2 3 4 5
    console.log(elem);
});

在上例是一個數值產生器,startInclusiveendInclusive 一定要提供,若不提供 step 的話,預設步進值就是 1。

如果在選項非常多時,還會採用選項物件(Option object)的方式。例如:

function func(option) {
    var opt = {
        x : option.x || 10,
        y : option.y || 20,
        z : option.z || 30
    };
    console.log(opt.x);
    console.log(opt.y);
    console.log(opt.z);
}

func({x : 100}); // 100 20 30

func({           // 100 200 30
    x : 100, 
    y : 200 
});

func({           // 100 200 300
    x : 100, 
    y : 200, 
    z : 300
});

在上例中,呼叫函式時必須提供物件,物件上帶有函式所需的資料,函式內部對於物件上沒有提供資料時,會提供預設值。