在脚本的许多地方,我们经常需要执行类似的操作。
例如,当访客登录、登出,或在其他场景中,我们可能都需要显示一条漂亮的消息。
函数是程序的主要“构建块”。它们允许我们多次调用相同的代码,而无需重复书写。JavaScript 中的函数是可以定义并在需要时调用的代码片段。它们可以被看作是程序中的子程序。
函数非常有用,因为它们允许程序员只编写一次代码,然后在程序中多次重复使用,从而减少代码重复。同时,函数也使程序员能够创建逻辑上独立的代码段,这些代码段可以与程序中的其他部分区分开来。如果需要更详细的了解函数,可参考C语言的函数介绍。
我们已经见过一些内建函数的例子,比如 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!"; // local variable alert( message ); } showMessage(); // Hello, I'm JavaScript! alert( message ); // <-- Error! The variable is local to the function
外部变量
函数也可以访问外部的变量,例如:
let userName = 'John'; function showMessage() { let message = 'Hello, ' + userName; alert(message); } showMessage(); // Hello, John
函数可以完全访问外部变量,也可以修改它。
例如:
let userName = 'John'; function showMessage() { userName = "Bob"; // (1) changed the outer variable let message = 'Hello, ' + userName; alert(message); } alert( userName ); // John before the function call showMessage(); alert( userName ); // Bob, the value was modified by the function
只有在函数内部没有声明同名的局部变量时,外部变量才会被使用。
如果在函数内部声明了一个同名的变量,它会遮蔽(shadow)外部的变量。例如,在下面的代码中,函数使用的是局部的 userName
,外部的 userName
被忽略了:
let userName = 'John'; function showMessage() { let userName = "Bob"; // declare a local variable let message = 'Hello, ' + userName; // Bob alert(message); } // the function will create and use its own userName showMessage(); alert( userName ); // John, unchanged, the function did not access the outer variable
全局变量
在任何函数外部声明的变量,比如上面代码中的外部 userName
,称为全局变量。
全局变量可以在任何函数中访问(除非被局部变量遮蔽)。
最好尽量减少使用全局变量。现代代码中全局变量较少或没有。大多数变量都驻留在它们各自的函数中。不过,有时全局变量可以用来存储项目级别的数据。
参数
我们可以通过参数将任意数据传递给函数。
在下面的例子中,函数有两个参数:from
和 text
。
function showMessage(from, text) { // parameters: 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 // the value of "from" is the same, the function modified a local copy alert( from ); // Ann
当一个值作为函数参数传递时,它也被称为实参。
换句话说,澄清这些术语的定义:
-
参数 是在函数声明时括号内列出的变量(这是声明时的术语)。
-
实参 是在调用函数时传递给函数的值(这是调用时的术语)。
我们声明函数时列出其参数,然后在调用时传递实参。
在上面的例子中,可以这样说:“showMessage
函数声明时有两个参数,然后在调用时传入了两个实参:from
和 "Hello"
。”
默认值
如果调用了一个函数,但未提供某个参数,则对应的值将变为 undefined。
例如,上述函数 showMessage(from, text) 可以只用一个参数进行调用:
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”。
如果参数存在但严格等于 undefined,默认值也会生效,如下所示:
showMessage("Ann", undefined); // Ann: no text given
这里的 “no text given” 是一个字符串,但它也可以是更复杂的表达式,且仅在参数缺失时才会被计算并赋值。因此,这也是可行的:
function showMessage(from, text = anotherFunction()) { // anotherFunction() only executed if no text given // its result becomes the value of text }
默认参数的求值
在 JavaScript 中,每次调用函数且未提供相应参数时,默认参数都会被求值。
在上面的例子中,如果提供了 text 参数,anotherFunction() 根本不会被调用。
另一方面,每次缺少 text 参数时,它都会被单独调用一次。
默认参数的替代
有时在函数声明之后的某个阶段为参数赋默认值更有意义。
我们可以在函数执行期间通过将参数与 undefined 进行比较来检查是否传入了该参数:
function showMessage(text) { // ... if (text === undefined) { // if the parameter is missing text = 'empty message'; } alert(text); } showMessage(); // empty message
……或者我们可以使用 || 运算符:
function showMessage(text) { // if text is undefined or otherwise falsy, set it to 'empty' text = text || 'empty'; ... }
现代 JavaScript 引擎支持空值合并运算符 ??,当大多数假值(如 0)应被视为“正常”时,它更合适:
function showCount(count) { // if count is undefined or null, show "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('Do you have permission from your parents?'); } } let age = prompt('How old are you?', 18); if ( checkAge(age) ) { alert( 'Access granted' ); } else { alert( 'Access denied' ); }
下面的示例在一个函数中使用多个 return
语句来根据条件返回不同的值:
function compare(a, b) { if (a > b) { return -1; } else if (a < b) { return 1; } return 0; }
compare()
函数比较两个值。它返回:
- 如果第一个参数大于第二个参数,则返回 -1 。
- 如果第一个参数小于第二个参数,则返回 1。
- 如果第一个参数等于第二个参数,则返回 0。
函数在到达 return
语句时立即停止执行。因此,您可以使用不带值的 return
语句来提前退出函数,如下所示:
例如:
function showMovie(age) { if ( !checkAge(age) ) { return; } alert( "Showing you the movie" ); // (*) // ... }
在上面的代码中,如果 checkAge(age) 返回 false,那么 showMovie 将不会继续执行 alert。
JavaScript 函数、变量、和常量的命名规范
1. 命名规范
ECMAScript 规范中标识符采用驼峰大小写格式,驼峰命名法由小(大)写字母开始,后续每个单词首字母都大写。根据首字母是否大写,分为两种方式:
- Pascal Case 大驼峰式命名法:首字母大写。eg:StudentInfo、UserInfo、ProductInfo
- Camel Case 小驼峰式命名法:首字母小写。eg:studentInfo、userInfo、productInfo
标识符,则包括变量、函数名、类名、属性名和函数或类的参数,每个命名方法又略有不同,下面详细解释一下:
2. 函数的命名
函数是执行某些操作的行为。因此,它们的名称通常是动词。名称应简洁、尽可能准确,并描述函数的作用,以便阅读代码的人能大致了解该函数的功能。
一个广泛采用的做法是用动词前缀来命名函数,这些前缀模糊地描述了动作。团队内部应就这些前缀的含义达成一致。
命名方法:小驼峰式命名法。
命名规范:前缀应当为动词。
命名建议:可使用常见动词约定
动词 | 含义 | 返回值 |
---|---|---|
can | 判断是否可执行某个动作(权限) | 函数返回一个布尔值。true:可执行;false:不可执行 |
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值 |
is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值 |
get | 获取某个值 | 函数返回一个非布尔值 |
set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |
load | 加载某些数据 | 无返回值或者返回是否加载完成的结果 |
以下是一些常见前缀及其含义:
-
“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
—— 如果它弹出一个显示年龄的警告框,那就不合适(它应该只是获取年龄)。 -
createForm
—— 如果它修改了文档并将表单添加到其中,那就不妥(它应该只是创建并返回表单)。 -
checkPermission
—— 如果它显示了“允许访问/拒绝访问”的消息,那就不好(它应该只执行检查并返回结果)。
这些例子基于前缀的一般含义。你和你的团队可以就其他含义达成一致,但通常不会有太大差别。无论如何,你应当清楚了解每个前缀的意义,知道带有该前缀的函数应该做什么、不能做什么。所有使用相同前缀的函数都应遵循同一规则,并在团队中达成共识。
超短的函数名称
那些非常常用的函数有时会使用超短的名称。
例如,jQuery 框架定义了一个函数,名称是 $。Lodash 库的核心函数名为 _。
这些是例外。通常情况下,函数名称应简洁且具有描述性。
函数 == 注释
函数应该简短并且只做一件事。如果这件事很大,也许值得将函数拆分成几个更小的函数。有时候遵循这个规则可能并不容易,但它绝对是有益的。
一个单独的函数不仅更容易测试和调试——它的存在本身就是一个很好的注释!
例如,比较下面的两个 showPrimes(n) 函数。每个函数都会输出小于等于 n 的质数。
下面第一段CODE使用了标签:
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 ); // a prime } }
第二段COD使用了一个额外的函数 isPrime(n) 来测试是否为质数:
function showPrimes(n) { for (let i = 2; i < n; i++) { if (!isPrime(i)) continue; alert(i); // a prime } } function isPrime(n) { for (let i = 2; i < n; i++) { if ( n % i == 0) return false; } return true; }
第二段code更容易理解,不是吗?我们看到的是一个动作的名称(isPrime),而不是一段代码。有时人们将这样的代码称为自描述(self-describing)代码。
因此,即使我们不打算重复使用它们,函数仍然可以被创建。它们帮助结构化代码并提高可读性。
3. 变量的命名
命名方法:小驼峰式命名法。
命名规范:前缀应当是名词。(函数的名字前缀为动词,以此区分变量和函数)
命名建议:尽量在变量名字中体现所属类型,如:length、count等表示数字类型;而包含name、title表示为字符串类型。
// 好的命名方式 let maxCount = 10; let tableTitle = 'LoginTable'; // 不好的命名方式 let setCount = 10; let getTitle = 'LoginTable';
4. 常量
命名方法:名称全部大写。
命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词。
const MAX_COUNT = 10; const URL = 'https://2743.com';
