函数

2018-02-26大约15分钟

函数是执行特定任务的单元,比如前面介绍过的parseInt()parseFloat()都是函数。

function关键字

定义一个函数通常很简单,可以使用function关键字定义函数:

function add(value1, value2) { // 定义函数add
    return value1 + value2;
}

const result = add(1, 2); // 调用函数add
console.log(result);

上面代码的各个部分如下图:

图片

在JavaScript中定义函数,有几个部分:

  • 函数名,位于function之后,和变量名的命名规则一样
  • 参数,在括号()里,可以没有参数,也可以多个参数
  • 函数体里面可以为空,也可以有多个语句

为了让代码易于理解,应该使用有意义的函数名。这样,以后在代码中用到这个函数的时候,就知道该函数的功能。

函数表达式

虽然前面的函数声明在语法上是一个语句,但函数也可以由函数表达式创建。这样的函数可以是匿名 的:它不必有一个名称。例如,函数add也可这样来定义:

const add = function (value1, value2) { // 创建一个匿名函数对象,并赋给变量add
    return value1 + value2;
};

const result = add(1, 2); // 调用函数add
console.log(result);

因此,我们也可以给对象动态添加函数属性,称之为方法

const calculator = {};
calculator.add = function (value1, value2) {
    return value1 + value2;
};

const result = calculator.add(1, 2); // 调用对象calculator的add方法
console.log(result);

箭头函数

在ES6里面,新加入了箭头函数。箭头函数表达式(也称胖箭头函数)相比函数表达式具有较短的语法并以词法的方式绑定 this。箭头函数总是匿名 的。

胖箭头长的是这样子的:=>,不要误以为是等于大于运算符,看一个例子:

const add = (value1, value2) => { // 创建一个箭头函数对象,并赋给变量add
    return value1 + value2;
};

const result = add(1, 2); // 调用函数add
console.log(result);

至于什么是绑定this,后面变量的作用域部分会讲解。

函数参数

Python的函数参数分为以下4种:

  1. 必要参数(Required arguments)
  2. 默认参数(Default arguments)
  3. 关键字参数(Keyword arguments)
  4. 变长参数(Variable-length arguments)

必要的参数(Required arguments)

一般情况下,一个函数定义的时候声明了几个参数,调用的时候就要传几个参数,并且要保证参数的顺序是和定义的要求一致。

比如:

def add(x, y):
    return x + y;

add(2)

第四行,我们调add函数只传了一个参数,因此程序就报错了。

默认参数(Default arguments)

我们在定义函数的时候,可以给某些参数赋一个默认值,这样调用的时候不传这个参数

def add(x, y=1):
    return x + y;

result = add(2)
print(result)

但是如果这么写,会是什么结果呢?

def add(x=1, y):
    return x + y;

add(2)

报错了!原因是有默认值得参数只能写到没有默认值参数的后面。

关键字参数(Default arguments)

如果只想传某些参数的值,而不用考虑实参的顺序,怎么办?那就用关键字参数。

def add(x, y, z=3):
    print('x: {0}'.format(x))
    return x + y + z;

result = add(y=2, x=1)
print('result: {0}'.format(result))

变长参数(Variable-length arguments)

有些时候,函数的参数数量不确定,那就可以用变长参数,变量名前面加一个星号*,实际传入的是一个tuple

def add(x, *args):
    print('x: {0}'.format(x))
    print('args: {0}'.format(args))
    result = x
    for arg in args:
        result += arg
    return result;

result = add(1, 2, 3, 4, 5, 6, 7)
print('result: {0}'.format(result))

当变量名前加两个星号**,则实际传入的是一个dict

def print_user(name, **args):
    print('x: {0}'.format(name))
    print('args: {0}'.format(args))

print_user('Tom', age=10, gender='male')

当用两个星号的时候,变长的参数部分一定要用关键字参数来作为字典的key。

编码风格探讨——过多的参数

有时候定义一个函数,可能会发现这个函数依赖五、六个甚至更多的参数,定义出来就是这样子:

function printRecord(firstName, lastName, dateOfBirth, streetAddress, postCode, city, country, email, website) { // 函数定义
    const result = {
        name: lastName + ', ' + firstName,
        DOB: dateOfBirth,
        address: streetAddress + '\n' + postCode + ' ' + city + '\n' + country,
        email: email,
        url: website
    };
    console.log(JSON.stringify(result));
}

printRecord("雷", "李", "2000-08-10", "某街某小区", "000000", "xx市", "中国", "email@duolaima.com", "unknown"); // 函数调用

这样的函数定义,有一些潜在的问题:

  • 调用这个printRecord函数的时候,很难记住要传哪些参数,以及参数的顺序,需要不断地查这个函数的接口定义才行,很麻烦
  • 将来要增加或删除一个参数的时候,调用的地方因为不太容易读,修改起来不太容易。

如何优化呢?那就是传一个对象,包含所有必须的参数:

function printRecord(record) {
    const result = {
        name: record.lastName + ", " + record.firstName,
        DOB: record.dateOfBirth,
        address: record.streetAddress + "\n"
            + record.postCode + " " + record.city + "\n"
            + record.country,
        email: record.email,
        url: record.website
    };
    console.log(JSON.stringify(result));
}

const record = {
    firstName: "雷",
    lastName: "李",
    dateOfBirth: "2000-08-10",
    streetAddress: "某街某小区",
    postCode: "000000",
    city: "xx市",
    country: "中国",
    email: "email@duolaima.com",
    website: "unknown"
};
printRecord(record);

虽然调用的时候,需要保证传入的是个对象,但这时候完全不用担心参数的顺序,甚至删除函数某个参数的时候,都不需要改调用的代码。

那么多少个参数算是过多呢?3个,5个还是7个?这个没有明确的规则,具体还要看代码的具体实现。不过,当你感觉传对象比挨个传参数会让你目前的代码更容易理解的时候,肯定是参数多了需要重构了。