手机店的店员可能不会拿着一个计算器到处走,用它来搞清税费和最终的购物款。这是一个她需要定义一次然后一遍又一遍地重用的任务。很有可能的是,公司有一个带有内建这些“功能”的收银机(电脑,平板电脑,等等)。

相似地,几乎可以肯定你的程序想要将代码的任务分割成可以重用的片段,而不是频繁地多次重复自己。这么做的方法是定义一个function

一个函数一般来说是一段被命名的代码,它可以使用名称来被“调用”,而每次调用它内部的代码就会运行。考虑如下代码:

  1. function printAmount() {
  2. console.log( amount.toFixed( 2 ) );
  3. }
  4. var amount = 99.99;
  5. printAmount(); // "99.99"
  6. amount = amount * 2;
  7. printAmount(); // "199.98"

函数可以选择性地接收参数值(也就是参数)—— 你传入的值。而且它们还可以选择性地返回一个值。

  1. function printAmount(amt) {
  2. console.log( amt.toFixed( 2 ) );
  3. }
  4. function formatAmount() {
  5. return "$" + amount.toFixed( 2 );
  6. }
  7. var amount = 99.99;
  8. printAmount( amount * 2 ); // "199.98"
  9. amount = formatAmount();
  10. console.log( amount ); // "$99.99"

函数printAmount(..)接收一个参数,我们称之为amt。函数formatAmount()返回一个值。当然,你也可以在同一个函数中组合这两种技术。

函数经常被用于你打算多次调用的代码,但它们对于仅将有关联的代码组织在一个命名的集合中也很有用,即便你只打算调用它们一次。

考虑如下代码:

  1. const TAX_RATE = 0.08;
  2. function calculateFinalPurchaseAmount(amt) {
  3. // 计算带有税费的新费用
  4. amt = amt + (amt * TAX_RATE);
  5. // 返回新费用
  6. return amt;
  7. }
  8. var amount = 99.99;
  9. amount = calculateFinalPurchaseAmount( amount );
  10. console.log( amount.toFixed( 2 ) ); // "107.99"

虽然calculateFinalPurchaseAmount(..)只被调用了一次,但是将它的行为组织进一个分离的带名称的函数,让使用它逻辑的代码(amount = calculateFinal...语句)更干净。如果函数中拥有更多的语句,这种好处将会更加明显。

作用域

如果你向手机店的店员询问一款她们店里没有的手机,那么她就不能卖给你你想要的。她只能访问她们店库房里的手机。你不得不到另外一家店里去看看能不能找到你想要的手机。

编程对这种概念有一个术语:作用域(技术上讲称为 词法作用域)。在JavaScript中,每个函数都有自己的作用域。作用域基本上就是变量的集合,也是如何使用名称访问这些变量的规则。只有在这个函数内部的代码才能访问这个函数 作用域内 的变量。

在同一个作用域内变量名必须是唯一的 —— 不能有两个不同的变量a并排出现。但是相同的变量名a可以出现在不同的作用域中。

  1. function one() {
  2. // 这个 `a` 仅属于函数 `one()`
  3. var a = 1;
  4. console.log( a );
  5. }
  6. function two() {
  7. // 这个 `a` 仅属于函数 `two()`
  8. var a = 2;
  9. console.log( a );
  10. }
  11. one(); // 1
  12. two(); // 2

另外,一个作用域可以嵌套在另一个作用域中,就像生日Party上的小丑在一个气球的里面吹另一个气球一样。如果一个作用域嵌套在另一个中,那么在内部作用域中的代码就可以访问这两个作用域中的变量。

考虑如下代码:

  1. function outer() {
  2. var a = 1;
  3. function inner() {
  4. var b = 2;
  5. // 我们可以在这里同时访问 `a` 和 `b`
  6. console.log( a + b ); // 3
  7. }
  8. inner();
  9. // 我们在这里只能访问 `a`
  10. console.log( a ); // 1
  11. }
  12. outer();

词法作用域规则说,在一个作用域中的代码既可以访问这个作用域中的变量,又可以访问任何在它外面的作用域的变量。

所以,在函数inner()内部的代码可以同时访问变量ab,但是仅在outer()中的代码只能访问a —— 它不能访问b因为这个变量仅存在于inner()内部。

回忆一下先前的这个代码段:

  1. const TAX_RATE = 0.08;
  2. function calculateFinalPurchaseAmount(amt) {
  3. // 计算带有税费的新费用
  4. amt = amt + (amt * TAX_RATE);
  5. // 返回新费用
  6. return amt;
  7. }

因为词法作用域,常数TAX_RATE(变量)可以从calculateFinalPurchaseAmount(..)函数中访问,即便它没有被传入这个函数。

注意: 关于词法作用域的更多信息,参见本系列的 作用域与闭包 的前三章。