现代 JavaScript 教程之函数基础篇

简介: 现代 JavaScript 教程之函数基础篇

640.png


函数


我们经常需要在脚本的许多地方执行很相似的操作。

例如,当访问者登录、注销或者在其他地方时,我们需要显示一条好看的信息。

函数是程序的主要“构建模块”。函数使该段代码可以被调用很多次,而不需要写重复的代码。

我们已经看到了内置函数的示例,如 alert(message)prompt(message, default)confirm(question)。但我们也可以创建自己的函数。


函数声明


使用 函数声明 创建函数。

看起来就像这样:


function showMessage() {
  alert( 'Hello everyone!' );
}


function 关键字首先出现,然后是 函数名,然后是括号之间的 参数 列表(用逗号分隔,在上述示例中为空),最后是花括号之间的代码(即“函数体”)。


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,都被称为 全局 变量。


全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。

减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。


参数

我们可以使用参数(也称“函数参数”)来将任意数据传递给函数。

在如下示例中,函数有两个参数:fromtext


function showMessage(from, text) { // 参数:from 和 text
  alert(from + ': ' + text);
}
showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)


当函数在 (*)(**) 行中被调用时,给定值被复制到了局部变量 fromtext。然后函数使用它们进行计算。

这里还有一个例子:我们有一个变量 from,并将它传递给函数。请注意:函数会修改 from,但在函数外部看不到更改,因为函数修改的是复制的变量值副本:


function showMessage(from, text) {
 from = '*' + from + '*'; // 让 "from" 看起来更优雅
  alert( from + ': ' + text );
}
letfrom = "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) {
    returntrue;
  } 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


空值的 returnreturn 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[1] 框架用 $ 定义一个函数。LoDash[2] 库的核心函数用 _ 命名。

这些都是例外,一般而言,函数名应简明扼要且具有描述性。


函数 == 注释


函数应该简短且只有一个功能。如果这个函数功能复杂,那么把该函数拆分成几个小的函数是值得的。有时候遵循这个规则并不是那么容易,但这绝对是件好事。

一个单独的函数不仅更容易测试和调试 —— 它的存在本身就是一个很好的注释!

例如,比较如下两个函数 showPrimes(n)。他们的功能都是输出到 n素数[3]

第一个变体使用了一个标签:


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) returnfalse;
  }
  returntrue;
}


第二个变体更容易理解,不是吗?我们通过函数名(isPrime)就可以看出函数的功能,而不需要通过代码。人们通常把这样的代码称为 自描述

因此,即使我们不打算重用它们,也可以创建函数。函数可以让代码结构更清晰,可读性更强。


总结


函数声明方式如下所示:


function name(parameters, delimited, by, comma) {
  /* code */
}


  • 作为参数传递给函数的值,会被复制到函数的局部变量。
  • 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
  • 函数可以返回值。如果没有返回值,则其返回的结果是 undefined

为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。

与不获取参数但将修改外部变量作为副作用的函数相比,获取参数、使用参数并返回结果的函数更容易理解。

函数命名:

  • 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
  • 一个函数是一个行为,所以函数名通常是动词。
  • 目前有许多优秀的函数名前缀,如 create…show…get…check… 等等。使用它们来提示函数的作用吧。

函数是脚本的主要构建块。现在我们已经介绍了基本知识,现在我们就可以开始创建和使用函数了。但这只是学习和使用函数的开始。我们将继续学习更多函数的相关知识,更深入地研究它们的先进特征。


作业题


先自己做题目再看答案。


1. 是否需要 “else”?


重要程度:⭐️⭐️⭐️⭐

如果参数 age 大于 18,那么下面的函数将返回 true

否则它将会要求进行确认,并返回确认结果:


function checkAge(age) {
  if (age > 18) {
    returntrue;
  } else {
    // ...
    return confirm('Did parents allow you?');
  }
}


如果 else 被删除,函数的工作方式会不同吗?


function checkAge(age) {
  if (age > 18) {
    returntrue;
  }
  // ...
  return confirm('Did parents allow you?');
}


这两个变体的行为是否有区别?

2. 使用 '?' 或者 '||' 重写函数


重要程度:⭐️⭐️⭐️⭐

如果参数 age 大于 18,那么下面的函数返回 true

否则它将会要求进行确认,并返回确认结果:


function checkAge(age) {
  if (age > 18) {
    returntrue;
  } else {
    return confirm('Do you have your parents permission to access this page?');
  }
}


重写这个函数并保证效果相同,不使用 if,且只需一行代码。

编写 checkAge 的两个变体:

  1. 使用问号运算符 ?
  2. 使用或运算符 ||


3. 函数 min(a, b)


重要程度:⭐

写一个返回数字 ab 中较小的那个数字的函数 min(a,b)

例如:


min(2, 5) == 2
min(3, -1) == -1
min(1, 1) == 1


4. 函数 pow(x,n)


重要程度:⭐️⭐️⭐️⭐

写一个函数 pow(x,n),返回 xn 次方。换句话说,将 x 与自身相乘 n 次,返回最终结果。


pow(3, 2) = 3 * 3 = 9
pow(3, 3) = 3 * 3 * 3 = 27
pow(1, 100) = 1 * 1 * ...*1 = 1


创建一个 web 页面,提示输入 xn,然后返回 pow(x,n) 的运算结果。

P.S. 在这个任务中,函数应该只支持自然数 n:从 1 开始的整数。

目录
相关文章
|
3天前
|
JavaScript 前端开发
JavaScript 闭包:让你更深入了解函数和作用域
JavaScript 闭包:让你更深入了解函数和作用域
|
4天前
|
JavaScript 前端开发 网络安全
【网络安全 | 信息收集】JS文件信息收集工具LinkFinder安装使用教程
【网络安全 | 信息收集】JS文件信息收集工具LinkFinder安装使用教程
16 4
|
2天前
|
JavaScript 前端开发 网络架构
函数柯里化:JavaScript中的高级技巧
函数柯里化:JavaScript中的高级技巧
|
3天前
|
JavaScript 前端开发
JavaScript的`apply`方法:函数的“应用”与“调用”
JavaScript的`apply`方法:函数的“应用”与“调用”
|
3天前
|
JavaScript 前端开发
JavaScript的`bind`方法:函数的“复制”与“定制”
JavaScript的`bind`方法:函数的“复制”与“定制”
|
3天前
|
JavaScript 前端开发
JavaScript的`call`方法:实现函数间的调用!
JavaScript的`call`方法:实现函数间的调用!
|
4天前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
18 2
|
4天前
|
存储 JavaScript 前端开发
Javascript教程
Javascript教程
9 0
|
4天前
|
前端开发 JavaScript 数据处理
在JavaScript中,异步函数是指什么
【5月更文挑战第9天】JavaScript中的异步函数用于处理非立即完成的操作,如定时器、网络请求等。它们可通过回调函数、Promise或async/await来实现。示例展示了如何使用async/await模拟网络请求:定义异步函数fetchData返回Promise,在另一异步函数processData中使用await等待结果并处理。当fetchData的Promise解析时,data变量接收结果并继续执行后续代码。注意,调用异步函数不会阻塞执行,而是会在适当时间点继续。
12 0
|
4天前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
14 1