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

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

函数是程序的主要“构建模块”,它们允许不重复地多次调用代码。

我们已经看到了内置函数的示例,如 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 );
}

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() 仅在没有给定文本时执行
  // 其结果变成文本值
}
默认参数的计算

在 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" 开头的函数通常会显示某些内容。

函数开始…

  • "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 ); // a prime
  }
}

第二个变体使用附加函数 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;
}

第二个变体更容易理解,不是吗?我们看到的不是代码块,而是操作的名称(isPrime)。有时人们把这样的代码称为自我描述

因此,即使我们不打算重用它们,也可以创建函数。它们构造代码并使其可读性强。

总结

函数声明像这样:

function name(parameters, delimited, by, comma) {
  /* code */
}
  • 作为参数传递给函数,值被复制到其局部变量
  • 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到它的局部变量。
  • 函数可以返回值。如果没有,则其结果是 undefined

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

与不获取参数但作为副作用修改外部变量的函数相比,理解获取参数、与它们一起工作并返回结果的函数总是更容易理解。

函数命名:

  • 名称应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的名称立即让我们了解它所做的和返回的事情。
  • 一个函数是一个行为,所以函数名通常是动词。
  • 有许多优秀的函数前缀,如 create…show…get…check… 等等。使用它们来提示函数的作用。

函数是脚本的主要构建块。现在我们已经介绍了基本知识,这样我们就可以开始创建和使用它们了。但这只是道路的开始。我们将多次回到它们身上,更深入地研究它们的先进特征。

任务

重要程度: 4

如果参数 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?');
}

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

没有什么不同。

重要程度: 4

如果参数 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 的两个变体:

  1. 使用 ? 运算符标记一个问题
  2. 使用 OR ||

使用 '?' 操作符标记一个问题:

function checkAge(age) {
  return (age > 18) ? true : confirm('Did parents allow you?');
}

使用 OR || (最短变体):

function checkAge(age) {
  return (age > 18) || confirm('Did parents allow you?');
}

请注意此处不需要 age > 18 附近的括号。它们的存在是为了更好的可读性。

重要程度: 1

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

例如:

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

使用 if 的解决方案:

function min(a, b) {
  if (a < b) {
    return a;
  } else {
    return b;
  }
}

使用 '?' 运算符的解决方案:

function min(a, b) {
  return a < b ? a : b;
}

P.S. 在等式 a == b 的情况下,返回的结果是无关紧要的。

重要程度: 4

写一个函数 pow(x,n),在 n 中返回 x。或者换句话说,将 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 开始加值。

function pow(x, n) {
  let result = x;

  for (let i = 1; i < n; i++) {
    result *= x;
  }

  return result;
}

let x = prompt("x?", '');
let n = prompt("n?", '');

if (n < 1) {
  alert(`Power ${n} is not supported, use a positive integer`);
} else {
  alert( pow(x, n) );
}
教程路线图

评论

在评论之前先阅读本内容…
  • 欢迎你在文章下添加补充内容、提出你的问题或回答提出的问题。
  • 使用 <code> 标签插入几行代码,对于多行代码 — 可以使用 <pre>,对于超过十行的代码 — 建议使用沙箱(plnkrJSBincodepen 等)。
  • 如果你无法理解文章中的内容 — 请详细说明。