原文链接:https://javascript.info/function-basics,translate with ❤️ by zhangbao.
我们通常要要在一个脚本里的许多地方进行同样一类操作。
例如,当访问者登录、注销或在其他地方登录时,我们需要显示一个好看的消息。
函数是程序的主要“构建块”。它们允许多次调用代码而不需要重复书写代码。
我们已经看到很多内置函数的例子,像 alert(message)、prompt(message, default) 和 confirm(question)。但我们也可以创造属于我们自己的函数。
函数声明
创建函数可以使用函数声明的形式。
像这样:
function showMessage() {
alert( 'Hello everyone!' );
}
首先是 function 关键字,然后紧随其后的是函数名,然后是圆括号里的一列参数(上面例子里是空的),最后是函数代码,称为“函数体”,包在花括号里面的。
我们的新函数可以用它的名字来调用: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 ); // <-- Error! 因为变量是在函数内可用的
外部变量
一个函数也可以访问外部变量,例如:
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
当本地变量不存在时,就会使用外部变量。所以,如果我们忘记加 let 的话,可能就会导致偶然修改。
如果在函数中声明了相同命名的变量,那么它就会这遮蔽部的。例如,下面代码里,函数使用了局部变量 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 + '*'; // make "from" look nicer
alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// "from" 还是一样的, 函数仅修改了本地副本
alert( from ); // Ann
默认值
如果参数没有提供,那么它的值就是 undefined。
例如,之前提及的函数 showMessage(from, text) 可以在调用时仅提供一个参数:
showMessage("Ann");
不会产生错误。这样的调用会输出 “Ann: undefined”,没有提供 text,所以就认为 text === undefined。
在这种情况下,如果想使用“默认”的 text,我们可以用 = 指定:
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不支持默认参数。所以有其他的方法来支持它们,你可以在旧的脚本中找到它们。
例如,明确检查是否为 undefined:
function showMessage(from, text) {
if (text === undefined) {
text = 'no text given';
}
alert( from + ": " + text );
}
或者是 || 运算符:
function showMessage(from, text) {
// if text is falsy then text gets the "default" value
text = text || 'no text given';
...
}
返回值
一个函数可以作为结果返回到调用代码中的值。
最简单的例子是一个函数,它求两个值的和:
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) 返回 false,showMovie 函数不会处理到 alert 这一句。
⚠️一个函数直接使用 return
或者不使用,就等同于 return undefined
如果一个函数没有返回值,就等同于返回 undefined:
function doNothing() { /* empty */ }
alert( doNothing() === undefined ); // true
空的 return
也等同于 return undefiend
:
function doNothing() {
return;
}
alert( doNothing() === undefined ); // true
⚠️不要再 return 和值之间换行
对于一个长的表达式,它可能很容易把它放在一个单独的行中,像这样:
return
(some + long + expression + or + whatever * f(a) + f(b))
这是行不通的,因为 JavaScript 在 return 后会有一个分号。它的工作原理是这样的:
return;
(some + long + expression + or + whatever * f(a) + f(b))
所以,它实际上变成了一个空的返回。我们应该把这个值放在同一行上。
命名函数
函数是操作,所以他们的名字通常是一个动词。它应该简短,但尽可能准确地描述函数的作用。为了让读代码的人更容易理解。
这是一种普遍的做法,用一种语言前缀来开始一个函数,它模糊地描述了动作。团队内部必须就前缀的含义达成一致。
例如,以“show”开头的函数通常显示一些东西。
函数以一下开头:
“get…”:返回一个值,
“calc…”:计算什么,
“create…”:创建什么,
“check…”:检查什么,并且返回一个布尔值,等。
这类名字的例子:
showMessage(..) // shows a message
getAge(..) // returns the age (gets it somehow)
calcSum(..) // calculates a sum and returns the result
createForm(..) // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false
有了前缀,对函数名的一瞥就能理解它所做的工作以及它返回的值。
⚠️一个函数,一个行为
一个函数应该按照它的名字来做,不要再做其他的了。
两个独立的动作通常应该有两个功能,即使它们通常被调用在一起(在这种情况下,我们可以做一个调用这两个函数的第三个函数)
打破这一规则的几个例子:
getAge:如果展示一个 alert 显示年龄的话就很坏了(应该只是获取)。
createForm:如果还修改了文档的话,向其添加表单的话就很坏了(应该只是创建并且返回它)。
checkPermission:如果显示 access granted/denied 信息的话就很坏了(应该只执行检查并返回结果)。
这些例子假设前缀的通用含义。他们对你的意义是由你和你的团队决定的。也许你的代码有不同的行为是很正常的。但是你应该对前缀的含义有一个明确的理解,一个前缀的函数可以做什么,不能做什么。所有同前缀的函数都应该遵守规则。团队应该分享这些知识。
⚠️ 极短函数名
经常使用的函数有时会有超短的名称。
例如,jQuery 框架定义了函数 $,LoDash 库将其核心函数命名成 _。
函数等于注释
函数应该是短的,并且只做一件事。如果这个东西很大,也许把这个函数分解成几个小的函数是值得的。有时候遵循这条规则可能并不那么容易,但这绝对是一件好事。
一个单独的函数不仅更容易测试和调试——它的存在是一个很好的注释!
例如,比较下面的两个函数 showPrimes(n),功能是一样的,都是输出 n 以下的所有质数:
第一个变体使用一个标签:
function showPrimes(n) {
nextPrime: for (let i = 2; i < n; i++) {
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
alert( i ); // 素数
}
}
第二个标题则使用例如另一个函数 isPrime(n) 来测试是否为素数:
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i); // a prime
}
}
// 是素数吗
function isPrime(i) {
for (let j = 2; j < i; j++) {
if ( i % j == 0) return false;
}
return true;
}
第二个变体更容易理解,不是吗?我们看到的不是代码片段而是动作的名称(isPrime)。有时人们把这种代码称为自我描述。
所以,即使我们不打算重用它们,也可以创建函数。它们构造代码并使其可读。
总结
函数声明如下:
function name(parameters, delimited, by, comma) {
/* 代码 */
}
作为参数给函数的值会被复制到局部变量里。
一个函数可以访问外部变量。但它只适用于内外。函数外面的代码不能看到它的局部变量。
一个函数可以返回一个值。如果它没有,那么它的结果就是 undefiend。
为了使代码干净且易于理解,建议在函数中使用局部变量和参数,而不是外部变量。
它总是更容易理解一个函数,它获取参数,与它们一起工作,并返回一个结果,而不是一个没有参数的函数,而是将外部变量作为副作用进行修改。
函数命名:
名称应该清楚地描述函数的作用。当我们在代码中看到一个函数调用时,一个好的名称会立即让我们理解它的作用和返回。
函数是一个动作,所以函数名通常是口头的。
有许多众所周知的函数前缀,像 create…,show…,get…,check… 等等。用它们来暗示一个函数的作用。
函数是脚本的主要构建块。现在我们已经介绍了基础知识,所以我们可以开始创建和使用它们。但这只是这条道路的开始。我们将会多次返回他们,更深入地研究他们的高级特性。
(完)