我们经常需要在脚本的许多地方执行很相似的操作。
例如,当访问者登录、注销或者在其他地方时,我们需要显示一条好看的信息。
函数是程序的主要“构建模块”。函数使该段代码可以被调用很多次,而不需要写重复的代码。
我们已经看到了内建函数的示例,如 alert(message)、prompt(message, default) 和 confirm(question)。但我们也可以创建自己的函数。
函数声明
使用 函数声明 创建函数。
看起来就像这样:
function showMessage() {
alert( 'Hello everyone!' );
}
function 关键字首先出现,然后是 函数名,然后是括号之间的 参数 列表(用逗号分隔,在上述示例中为空,我们将在接下来的示例中看到),最后是花括号之间的代码(即“函数体”)。
function name(parameter1, parameter2, ... parameterN) {
...body...
}
我们的新函数可以通过名称调用:showMessage()。
例如:
function showMessage() {
alert( 'Hello everyone!' );
}
showMessage();
showMessage();
调用 showMessage() 执行函数的代码。这里我们会看到显示两次消息。
这个例子清楚地演示了函数的主要目的之一:避免代码重复。
如果我们需要更改消息或其显示方式,只需在一个地方修改代码:输出它的函数。
局部变量
在函数中声明的变量只在该函数内部可见。
例如:
function showMessage() {
let message = "Hello, I'm JavaScript!"; // 局部变量
alert( message );
}
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- 错误!变量是函数的局部变量
外部变量
函数也可以访问外部变量,例如:
let userName = 'John';
function showMessage() {
let message = 'Hello, ' + userName;
alert(message);
}
showMessage(); // Hello, John
函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。
例如:
let userName = 'John';
function showMessage() {
userName = "Bob"; // (1) 改变外部变量
let message = 'Hello, ' + userName;
alert(message);
}
alert( userName ); // John 在函数调用之前
showMessage();
alert( userName ); // Bob,值被函数修改了
只有在没有局部变量的情况下才会使用外部变量。
如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量。例如,在下面的代码中,函数使用局部的 userName,而外部变量被忽略:
let userName = 'John';
function showMessage() {
let userName = "Bob"; // 声明一个局部变量
let message = 'Hello, ' + userName; // Bob
alert(message);
}
// 函数会创建并使用它自己的 userName
showMessage();
alert( userName ); // John,未被更改,函数没有访问外部变量。
任何函数之外声明的变量,例如上述代码中的外部变量 userName,都被称为 全局 变量。
全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。
减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。
参数
我们可以通过参数将任意数据传递给函数。
在如下示例中,函数有两个参数:from 和 text。
function showMessage(from, text) { // 参数:from 和 text
alert(from + ': ' + text);
}
showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)
当函数在 (*) 和 (**) 行中被调用时,给定值被复制到了局部变量 from 和 text。然后函数使用它们进行计算。
这里还有一个例子:我们有一个变量 from,并将它传递给函数。请注意:函数会修改 from,但在函数外部看不到更改,因为函数修改的是复制的变量值副本:
function showMessage(from, text) {
from = '*' + from + '*'; // 让 "from" 看起来更优雅
alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// "from" 值相同,函数修改了一个局部的副本。
alert( from ); // Ann
当一个值被作为函数参数(parameter)传递时,它也被称为 参数(argument)。
换一种方式,我们把这些术语搞清楚:
- 参数(parameter)是函数声明中括号内列出的变量(它是函数声明时的术语)。
- 参数(argument)是调用函数时传递给函数的值(它是函数调用时的术语)。
我们声明函数时列出它们的参数(parameters),然后调用它们传递参数(arguments)。
在上面的例子中,我们可以说:“函数 showMessage 被声明,并且带有两个参数(parameters),随后它被调用,两个参数(arguments)分别为 from 和 "Hello"”。
默认值
如果一个函数被调用,但有参数(argument)未被提供,那么相应的值就会变成 undefined。
例如,之前提到的函数 showMessage(from, text) 可以只使用一个参数(argument)调用:
showMessage("Ann");
那不是错误,这样调用将输出 "*Ann*: undefined"。因为参数 text 的值未被传递,所以变成了 undefined。
我们可以使用 = 为函数声明中的参数指定所谓的“默认”(如果对应参数的值未被传递则使用)值:
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given
现在如果 text 参数未被传递,它将会得到值 "no text given"。
这里 "no text given" 是一个字符串,但它可以是更复杂的表达式,并且只会在缺少参数时才会被计算和分配。所以,这也是可能的:
function showMessage(from, text = anotherFunction()) {
// anotherFunction() 仅在没有给定 text 时执行
// 其运行结果将成为 text 的值
}
在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。
在上面的例子中,如果传递了参数 text,那么 anotherFunction() 就不会被调用。
如果没传递参数 text,那么 anotherFunction() 就会被调用。
几年前,JavaScript 不支持默认参数的语法。所以人们使用其他方式来设置默认参数。
如今,我们会在旧代码中看到它们。
例如,显式地检查 undefined:
function showMessage(from, text) {
if (text === undefined) {
text = 'no text given';
}
alert( from + ": " + text );
}
……或者使用 || 运算符:
function showMessage(from, text) {
// 如果 text 的值为假值,则分配默认值
// 这样赋值 text == "" 与 text 无值相同
text = text || 'no text given';
...
}
后备的默认参数
有些时候,将参数默认值的设置放在函数执行(相较更后期)而不是函数声明时,也行得通。
我们可以通过将参数与 undefined 进行比较,来检查该参数是否在函数执行期间被传递进来:
function showMessage(text) {
// ...
if (text === undefined) { // 如果参数未被传递进来
text = 'empty message';
}
alert(text);
}
showMessage(); // empty message
……或者我们可以使用 || 运算符:
function showMessage(text) {
// 如果 text 为 undefined 或者为假值,那么将其赋值为 'empty'
text = text || 'empty';
...
}
现代 JavaScript 引擎支持 空值合并运算符 ??,它在大多数假值(例如 0)应该被视为“正常值”时更具优势:
function showCount(count) {
// 如果 count 为 undefined 或 null,则提示 "unknown"
alert(count ?? "unknown");
}
showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown
返回值
函数可以将一个值返回到调用代码中作为结果。
最简单的例子是将两个值相加的函数:
function sum(a, b) {
return a + b;
}
let result = sum(1, 2);
alert( result ); // 3
指令 return 可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 result)。
在一个函数中可能会出现很多次 return。例如:
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('Got a permission from the parents?');
}
}
let age = prompt('How old are you?', 18);
if ( checkAge(age) ) {
alert( 'Access granted' );
} else {
alert( 'Access denied' );
}
只使用 return 但没有返回值也是可行的。但这会导致函数立即退出。
例如:
function showMovie(age) {
if ( !checkAge(age) ) {
return;
}
alert( "Showing you the movie" ); // (*)
// ...
}
在上述代码中,如果 checkAge(age) 返回