不可轻忽的函数基础


对于要重复执行的内容,你可以使用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
});

在上例中,调用函数时必须提供对象,对象上带有函数所需的数据,函数内部对于对象上没有提供数据时,会提供默认值。


展开阅读全文