Javascript 零基础教程

JavaScript 函数定义

到目前为止,我们探讨了如何用变量存储数据,用条件语句做决策,以及用循环重复执行操作。

然而,随着程序变得越来越大,你会发现自己经常需要多次执行同一套操作,或者需要更结构化地组织代码。这就是 函数 (Functions) 派上用场的地方。

函数允许你将一段代码打包,给它起个名字,然后在需要的时候随时运行它。这样不仅避免了重复,还能让你的代码更易读、易懂、易维护。

你可以把函数想象成主程序中的“迷你程序”,每个函数都为了完成特定的任务而设计。在本章节中,我们将深入学习如何定义这些强大的可重用代码块,重点关注它们如何通过 参数 (Parameters) 接收输入,并通过 返回值 (Return Values) 提供输出。

1. 定义函数

在 JavaScript 中,定义函数最常见的方法是使用 函数声明

它以 function 关键字开头,紧接着是函数名,然后是括号 () 包裹的参数列表,最后是花括号 {} 包裹的需要执行的代码。

让我们拆解一下基本语法:

function functionName(parameter1, parameter2, /* ... */) {
  // 要执行的代码
  // 这里是函数体
}
  • function: 这个关键字告诉 JavaScript 你正在定义一个函数。
  • functionName: 这是你为函数取的唯一标识符。最好的做法是取一个能描述函数功能的名称(例如:calculateSum 计算总和、displayMessage 显示消息、getUserInput 获取用户输入)。
  • paramter1, parameter2...: 这些是可选的占位符,用于接收你想传入函数的值。如果函数不需要任何输入,你仍然需要写上一对空的括号 ()
  • { ... }: 这是函数体,你在这里编写函数被调用时要执行的所有 JavaScript 语句。

函数定义好后,它不会自动运行。你需要 调用 (Call)执行 (Invoke) 它,代码才会跑起来。调用函数很简单,只需使用函数名加上括号 ()

function sayHello() {
  // 这个函数只是简单地打印一条消息
  console.log("你好,世界!");
}

// 调用函数以执行其代码
sayHello(); // 输出: 你好,世界!
sayHello(); // 输出: 你好,世界! (可以被多次调用)

在这个简单的例子中,sayHello 是一个不接受任何输入(参数)也不显式返回任何输出(返回值)的函数,它只是封装了“向世界问好”这个动作。

2. 代码复用的重要性

回想一下我们在第 3 模块中的成绩计算器练习。如果你需要为多名学生计算成绩,你不得不重复编写 if/else if 逻辑。函数允许你只写一次逻辑,然后反复使用。

// 不使用函数 (代码重复)
let student1Score = 85;
if (student1Score >= 90) {
  console.log("学生 1 成绩: A");
} else if (student1Score >= 80) {
  console.log("学生 1 成绩: B");
} else {
  console.log("学生 1 成绩: C"); // 为简洁起见进行了简化
}

let student2Score = 72;
if (student2Score >= 90) {
  console.log("学生 2 成绩: A");
} else if (student2Score >= 80) {
  console.log("学生 2 成绩: B");
} else {
  console.log("学生 2 成绩: C"); // 为简洁起见进行了简化
}

// 使用函数 (可复用)
function displayGrade(score) {
  if (score >= 90) {
    console.log("成绩: A");
  } else if (score >= 80) {
    console.log("成绩: B");
  } else {
    console.log("成绩: C"); // 为简洁起见进行了简化
  }
}

displayGrade(85); // 输出: 成绩: B
displayGrade(72); // 输出: 成绩: C
displayGrade(95); // 输出: 成绩: A

如你所见,定义 displayGrade 让代码变得更干净、更易于管理。如果你需要修改成绩的计算规则,你只需要在函数定义这一处进行修改即可。

3. 理解参数 (Parameters)

参数 本质上是列在函数定义中的变量。它们充当了占位符,代表了你在调用函数时打算传入的值。

这些具体传入的值被称为 参数值 (Arguments)。通过这种方式,函数可以处理特定的数据,而不需要在函数体内把数据写死。

3.1 定义参数

你在函数名后的括号 () 内列出参数,如果有多个参数,用逗号分隔。

function greet(name) { // 'name' 是一个参数
  console.log("你好," + name + "!");
}

greet 函数中,name 就是一个参数。当你调用这个函数时,你将为 name 提供一个具体的值。

3.2 传递参数值 (Arguments)

当你调用函数时,在括号内提供的值叫做 参数值 (Arguments)。这些值会被传递给函数,并按照顺序赋值给对应的参数。

// 调用 greet 函数并传递参数值
greet("Alice");    // 输出: 你好,Alice!
greet("Bob");      // 输出: 你好,Bob!

let userName = "Charlie";
greet(userName);   // 输出: 你好,Charlie!

在这里,"Alice""Bob" 以及变量 userName 的值(即 "Charlie")都是参数值 (Arguments)。当 greet("Alice") 被调用时,字符串 "Alice" 在该次执行中被赋值给了函数内部的 name 参数。

3.3 多个参数的函数

函数可以拥有任意数量的参数,每个参数之间用逗号隔开。

function addNumbers(num1, num2) { // num1 和 num2 是参数
  let sum = num1 + num2;
  console.log("总和是: " + sum);
}

addNumbers(5, 10);   // 输出: 总和是: 15
addNumbers(100, 200); // 输出: 总和是: 300

let x = 7;
let y = 3;
addNumbers(x, y);    // 输出: 总和是: 10

addNumbers(5, 10) 被调用时,在该次函数调用中,num1 变成了 5,而 num2 变成了 10

3.4 默认参数 (现代 JavaScript)

有时,你可能希望某个参数在调用时如果没有收到值,就使用一个默认值。这是现代 JavaScript 的一个特性,能让你的函数更灵活。

function sayGoodbye(name = "访客") { // '访客' 是 'name' 的默认值
  console.log("再见," + name + "!");
}

sayGoodbye("David");  // 输出: 再见,David!
sayGoodbye();         // 输出: 再见,访客! (未提供参数,使用默认值)
sayGoodbye(null);     // 输出: 再见,null! (null 是一个明确的值,不使用默认值)
sayGoodbye(undefined); // 输出: 再见,访客! (undefined 会明确触发默认值)

当函数可能在多种场景下被调用,且某些场景不一定能提供所有参数时,默认参数非常有用。

4. 理解返回值 (Return Values)

如果说参数允许函数接收输入,那么 返回值 则允许函数将结果发送回调用它的代码部分。

这对于创建那些主要用于计算或处理数据,并供程序后续使用的函数来说至关重要。

4.1 return 关键字

return 关键字在函数内部使用,用于指定函数应该输出的值。当 JavaScript 遇到 return 语句时,会发生两件事:

  1. 函数 立即停止执行。函数体内 return 语句之后的任何代码都不会运行。
  2. 指定的值(如果没有指定值则为 undefined)会被发送回函数被调用的地方。

让我们修改 addNumbers 例子,让它 返回 (return) 总和,而不是仅仅打印它:

function add(num1, num2) {
  let sum = num1 + num2;
  return sum; // 函数现在返回总和
  console.log("这行代码不会运行!"); // return 之后的代码是不可达的
}

// 将返回的值存储在一个变量中
let result = add(5, 10);
console.log("总和是: " + result); // 输出: 总和是: 15

// 直接使用返回的值
console.log("另一个总和: " + add(100, 200)); // 输出: 另一个总和: 300

// 你可以在其他计算或条件中使用返回值
let total = add(2, 3) + add(4, 5); // 相当于 5 + 9
console.log("总和的总和: " + total); // 输出: 总和的总和: 14

在这个例子中,add 函数计算 num1 + num2 然后 返回 那个 sum。当我们调用 add(5, 10) 时,值 15 被返回出来,我们可以把它赋值给变量 result 或者直接使用它。

4.2 如果函数不返回任何东西会怎样?

如果一个函数没有显式的 return 语句,或者只有 return; 而后面没有跟值,它会隐式地返回 undefined

function doNothing() {
  // 没有 return 语句
  console.log("我在这里没干什么正经事!");
}

let returnValue = doNothing();
console.log(returnValue); // 输出: 我在这里没干什么正经事! (来自函数内的 console.log)
                          // 输出: undefined (实际的返回值)

理解这个区别很重要。函数可以执行动作(如打印日志、修改外部变量等)而不显式返回值。但是,如果你需要 使用 函数计算的结果,你 必须 使用 return

5. 现实生活中的类比:参数与返回值

想象一台 咖啡机

  • 函数 (Function): 整个咖啡机本身。它的目的是制作咖啡。
  • 参数 (Parameters): 你需要提供的设置选项。例如:咖啡豆种类、水量、浓度设置、是否加糖等。没有这些输入,机器不知道该做什么咖啡。
  • 参数值 (Arguments): 你为参数选择的具体值。比如:“深烘焙豆”、“150毫升水”、“特浓”、“不加糖”。
  • 返回值 (Return Value): 机器的产出——一杯咖啡。咖啡是机器根据你的输入工作后的 结果。如果机器不 返回 一杯咖啡,即使它内部处理得再好,对你也没什么用。

另一个例子,租车服务

  • 函数: rentCar() (租车)
  • 参数: carType (车型), rentalDays (天数), customerName (客户名)
  • 参数值: ("SUV", 7, "张三")
  • 返回值: 一个租赁确认号和一把 SUV 的车钥匙。

6. 实践示例与演示

让我们结合参数和返回值,构建一些更复杂实用的场景。

6.1 示例 1:计算矩形面积

这个函数接收两个参数,length (长) 和 width (宽),并返回它们的乘积。

function calculateRectangleArea(length, width) {
  // 输入验证 (好习惯,基于第 3 模块的知识)
  if (length <= 0 || width <= 0) {
    console.warn("长度和宽度必须是正数值。");
    return 0; // 对于无效尺寸返回 0
  }
  const area = length * width;
  return area;
}

let livingRoomArea = calculateRectangleArea(10, 5);
console.log("客厅面积:", livingRoomArea, "平方单位"); // 输出: 客厅面积: 50 平方单位

let officeArea = calculateRectangleArea(7, 8.5);
console.log("办公室面积:", officeArea, "平方单位"); // 输出: 办公室面积: 59.5 平方单位

let invalidArea = calculateRectangleArea(-2, 5); // 输出: 长度和宽度必须是正数值。
console.log("无效面积:", invalidArea);        // 输出: 无效面积: 0

在这里,我们不仅仅是打印面积,而是让程序中的其他部分可以使用这个面积数据。例如,你可以随后用 livingRoomArea 来计算需要多少油漆或地板砖。

6.2 示例 2:检查数字是否为偶数

这个函数接收一个参数 number,并使用第 2 模块中的取模运算符 (%) 来判断它是否是偶数,返回 truefalse

function isEven(number) {
  // 我们使用取余运算符 (%) 来检查数字是否能被 2 整除。
  // 如果余数是 0,它就是一个偶数。
  return number % 2 === 0;
}

console.log("4 是偶数吗?", isEven(4));    // 输出: 4 是偶数吗? true
console.log("7 是偶数吗?", isEven(7));    // 输出: 7 是偶数吗? false
console.log("0 是偶数吗?", isEven(0));    // 输出: 0 是偶数吗? true
console.log("-2 是偶数吗?", isEven(-2)); // 输出: -2 是偶数吗? true

isEven 函数具有高度的可重用性。你可以在 if 语句或其他逻辑操作中直接使用它的返回值:

let myNumber = 13;
if (isEven(myNumber)) { // 直接在条件中使用 isEven 的返回值
  console.log(myNumber + " 是一个偶数。");
} else {
  console.log(myNumber + " 是一个奇数。"); // 输出: 13 是一个奇数。
}

6.3 示例 3:生成个性化问候语

这个函数接收 name (名字) 和一个可选的 salutation (称呼,带有默认值),并返回一个完整的问候字符串。这结合了参数、默认参数和第 2 模块中的字符串拼接/模板字面量。

function generateGreeting(name, salutation = "你好") {
  // 使用模板字面量可以轻松构建字符串 (第 2 模块知识)
  return `${salutation},${name}!欢迎光临。`;
}

let greeting1 = generateGreeting("Maria");
console.log(greeting1); // 输出: 你好,Maria!欢迎光临。

let greeting2 = generateGreeting("David", "早上好");
console.log(greeting2); // 输出: 早上好,David!欢迎光临。

let greeting3 = generateGreeting("Sarah", "嗨");
console.log(greeting3); // 输出: 嗨,Sarah!欢迎光临。