咱们常常须要在脚本的许多地方执行很类似的操做。javascript
例如,当访问者登陆、注销或者在其余地方时,咱们须要显示一条好看的信息。html
函数是程序的主要“构建模块”。函数使该段代码能够被调用不少次,而不须要写重复的代码。java
咱们已经看到了内置函数的示例,如 alert(message)
、prompt(message, default)
和 confirm(question)
。但咱们也能够建立本身的函数。react
使用 函数声明 建立函数。jquery
看起来就像这样:web
function showMessage() {
alert( 'Hello everyone!' );
}
复制代码
function
关键字首先出现,而后是 函数名,而后是括号之间的 参数 列表(用逗号分隔,在上述示例中为空),最后是花括号之间的代码(即“函数体”)。express
function name(parameters) {
...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
复制代码
若是未提供参数,那么其默认值则是 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 中,每次函数在没带个别参数的状况下被调用,默认参数会被计算出来。
在上面的例子中,每次 showMessage()
不带 text
参数被调用时,anotherFunction()
就会被调用。
旧版本的 JavaScript 不支持默认参数。因此在大多数旧版本的脚本中,你能找到其余设置默认参数的方法。
例如,用于 undefined
的显式检查:
function showMessage(from, text) {
if (text === undefined) {
text = 'no text given';
}
alert( from + ": " + text );
}
复制代码
……或使用 ||
运算符:
function showMessage(from, text) {
// 若是 text 能转为 false,那么 text 会获得“默认”值
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() { /* 没有代码 */ }
alert( doNothing() === undefined ); // true
复制代码
空值的 return
和 return undefined
等效:
function doNothing() {
return;
}
alert( doNothing() === undefined ); // true
复制代码
return
与返回值之间添加新行对于 return
的长表达式,可能你会很想将其放在单独一行,以下所示:
return
(some + long + expression + or + whatever * f(a) + f(b))
复制代码
但这不行,由于 JavaScript 默认会在 return
以后加上分号。上面这段代码和下面这段代码运行流程相同:
return;
(some + long + expression + or + whatever * f(a) + f(b))
复制代码
所以,实际上它的返回值变成了空值。
若是咱们想要将返回的表达式写成跨多行的形式,那么应该在 return
的同一行开始写此表达式。或者至少按照以下的方式放上左括号:
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
)
复制代码
而后它就能像咱们预想的那样正常运行了。
函数是行为。因此它们的名字一般是动词。它应该简短且尽量准确地描述函数的做用。这样读代码的人就能清楚地知道这个函数的功能。
一种广泛的作法是用动词前缀来开始一个函数,这个前缀模糊地描述了这个动做。团队内部必须就前缀的含义达成一致。
例如,以 "show"
开头的函数一般会显示某些内容。
函数以 XX 开始……
"get…"
—— 返回一个值,"calc…"
—— 计算某些内容,"create…"
—— 建立某些内容,"check…"
—— 检查某些内容并返回 boolean 值,等。这类名字的示例:
showMessage(..) // 显示信息
getAge(..) // 返回 age(gets it somehow)
calcSum(..) // 计算求和并返回结果
createForm(..) // 建立表格(一般会返回它)
checkPermission(..) // 检查权限并返回 true/false
复制代码
有了前缀,只需瞥一眼函数名,就能够了解它的功能是什么,返回什么样的值。
一个函数应该只包含函数名所指定的功能,而不是作更多与函数名无关的功能。
两个独立的操做一般须要两个函数,即便它们一般被一块儿调用(在这种状况下,咱们能够建立第三个函数来调用这两个函数)。
有几个违反这一规则的例子:
getAge
—— 若是它经过 alert
将 age 显示出来,那就有问题了(只应该是获取)。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); // 一个素数
}
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if ( n % i == 0) return false;
}
return true;
}
复制代码
第二个变体更容易理解,不是吗?咱们经过函数名(isPrime
)就能够看出函数的功能,而不须要经过代码。人们一般把这样的代码称为 自描述。
所以,即便咱们不打算重用它们,也能够建立函数。函数可让代码结构更清晰,可读性更强。
函数声明方式以下所示:
function name(parameters, delimited, by, comma) {
/* code */
}
复制代码
undefined
。为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。
与不获取参数但将修改外部变量做为反作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。
函数命名:
create…
、show…
、get…
、check…
等等。使用它们来提示函数的做用吧。函数是脚本的主要构建块。如今咱们已经介绍了基本知识,如今咱们就能够开始建立和使用函数了。但这只是学习和使用函数的开始。咱们将继续学习更多函数的相关知识,更深刻地研究它们的先进特征。
先本身作题目再看答案。
重要程度:⭐️⭐️⭐️⭐
若是参数 age
大于 18
,那么下面的函数将返回 true
。
不然它将会要求进行确认,并返回确认结果:
function checkAge(age) {
if (age > 18) {
return true;
} else {
// ...
return confirm('Did parents allow you?');
}
}
复制代码
若是 else
被删除,函数的工做方式会不一样吗?
function checkAge(age) {
if (age > 18) {
return true;
}
// ...
return confirm('Did parents allow you?');
}
复制代码
这两个变体的行为是否有区别?
重要程度:⭐️⭐️⭐️⭐
若是参数 age
大于 18
,那么下面的函数返回 true
。
不然它将会要求进行确认,并返回确认结果:
function checkAge(age) {
if (age > 18) {
return true;
} else {
return confirm('Do you have your parents permission to access this page?');
}
}
复制代码
重写这个函数并保证效果相同,不使用 if
,且只需一行代码。
编写 checkAge
的两个变体:
?
||
重要程度:⭐
写一个返回数字 a
和 b
中较小的那个数字的函数 min(a,b)
。
例如:
min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1
复制代码
重要程度:⭐️⭐️⭐️⭐
写一个函数 pow(x,n)
,返回 x
的 n
次方。换句话说,将 x
与自身相乘 n
次,返回最终结果。
pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...*1 = 1
复制代码
建立一个 web 页面,提示输入 x
和 n
,而后返回 pow(x,n)
的运算结果。
P.S. 在这个任务中,函数应该只支持天然数 n
:从 1
开始的整数。
在微信公众号「技术漫谈」后台回复 1-2-14
获取本题答案。
现代 JavaScript 教程:开源的现代 JavaScript 从入门到进阶的优质教程。React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程。
在线免费阅读:zh.javascript.info
扫描下方二维码,关注微信公众号「技术漫谈」,订阅更多精彩内容。