Javascript 零基础教程

JavaScript 函数传递参数与值引用

向函数传递参数,我们可以创建可重用且动态的代码。通过传递参数,我们可以每次在调用函数时定制它的行为,使我们的函数更加通用,能够适应不同的情况。

本章节将探讨如何有效地向函数传递参数,确保你扎实地理解这一核心编程原则。

1. 理解实参 (Arguments) 和形参 (Parameters)

实参 (Arguments) 是你在调用函数时传递给它的实际值。
形参 (Parameters) 是函数定义中圆括号内列出的变量。

形参充当了将来被传入的实参的占位符

看看这个比喻:想象一台自动售货机(函数)。机器上的按钮就是形参(例如 "A1", "B2", "C3")。当你按下一个按钮(调用函数)时,你提供了一个实参——即你想要的具体选择。然后机器会根据那个按钮分发对应的商品。

// 带参数的函数定义:
// name 和 greeting 是形参
function greet(name, greeting) {
  console.log(greeting + ", " + name + "!");
}

// 带参数的函数调用:
// "爱丽丝" 和 "你好" 是第一次调用时传递的实参
greet("爱丽丝", "你好"); // 输出: 你好, 爱丽丝!

// "鲍勃" 和 "早上好" 是再次调用时传递的实参
greet("鲍勃", "早上好"); // 输出: 早上好, 鲍勃!

在上面的例子中:

  • namegreetinggreet 函数的 形参 (Parameters)
  • "爱丽丝" 和 "你好" 是函数第一次被调用时传入的 实参 (Arguments)
  • "鲍" 和 "早上好" 是函数再次被调用时传入的 实参 (Arguments)

2. 传递不同的数据类型作为参数

JavaScript 是一种动态类型语言,这意味着你在定义函数时不需要显式声明参数的数据类型。你可以传递各种数据类型作为参数:

function displayInfo(name, age, isStudent) {
  console.log("姓名: " + name);
  console.log("年龄: " + age);
  console.log("是否为学生: " + isStudent);
}

displayInfo("查理", 20, true);
// 输出:
// 姓名: 查理
// 年龄: 20
// 是否为学生: true

displayInfo("达娜", 25, false);
// 输出:
// 姓名: 达娜
// 年龄: 25
// 是否为学生: false

你也可以传递对象和数组作为参数:

function printAddress(address) {
  console.log("地址: " + address.street + ", " + address.city + ", " + address.country);
}

const myAddress = {
  street: "主街 123 号",
  city: "任意城",
  country: "美国"
};

printAddress(myAddress); // 输出: 地址: 主街 123 号, 任意城, 美国

function sumArray(numbers) {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

const numberList = [1, 2, 3, 4, 5];
const total = sumArray(numberList);
console.log("总和: " + total); // 输出: 总和: 15

3. 实参数量 vs. 形参数量

JavaScript 允许你调用函数时,传入的实参数量多于或少于函数定义时的形参数量。

参数过少: 如果你传入的实参少于形参,缺失的形参将被赋值为 undefined

function describePerson(name, age, occupation) {
  console.log("姓名: " + name);
  console.log("年龄: " + age);
  console.log("职业: " + occupation);
}

describePerson("伊芙", 30);
// 输出:
// 姓名: 伊芙
// 年龄: 30
// 职业: undefined

参数过多: 如果你传入的实参多于形参,多余的实参可以通过函数内部的 arguments 对象访问(虽然这是一个旧特性,现在更推荐使用剩余参数 rest parameters,我们稍后会学到)。

function displayFirstTwo(a, b) {
  console.log("第一个: " + a);
  console.log("第二个: " + b);
  console.log("参数总数: " + arguments.length);
}

displayFirstTwo(1, 2, 3, 4, 5);
// 输出:
// 第一个: 1
// 第二个: 2
// 参数总数: 5

4. 按值传递 vs. 按引用传递

在 JavaScript 中,根据参数的数据类型不同,参数传递给函数的方式分为按值传递 (Passing by Value)按引用传递 (Passing by Reference)

4.1 按值传递 (Passing by Value)

原始数据类型(如数字 number、字符串 string、布尔值 boolean、nullundefined)是按值传递的。

这意味着会创建参数值的一个副本并传给函数。在函数内部对参数进行修改,不会影响函数外部的原始参数。

function changeValue(x) {
  x = 10;
  console.log("函数内部: x = " + x); // 输出: 函数内部: x = 10
}

let y = 5;
changeValue(y);
console.log("函数外部: y = " + y); // 输出: 函数外部: y = 5

在这个例子中,即使我们在 changeValue 函数内部改变了 x 的值,函数外部 y 的值依然保持不变。

4.2 按引用传递 (Passing by Reference)

对象(包括数组和函数)是按引用传递的。

这意味着函数接收的是指向内存中原始对象的引用(或指针),而不是对象本身的副本。在函数内部对对象属性的修改将会影响函数外部的原始对象。

function changeObject(obj) {
  obj.name = "鲍勃";
  console.log("函数内部: obj.name = " + obj.name); // 输出: 函数内部: obj.name = 鲍勃
}

let myObj = { name: "爱丽丝" };
changeObject(myObj);
console.log("函数外部: myObj.name = " + myObj.name); // 输出: 函数外部: myObj.name = 鲍勃

在这个例子中,changeObject 函数修改了 myObj 对象的 name 属性。因为对象是按引用传递的,所以这个改变也反映在函数外部。

重要提示: 如果你在函数内部重新给对象本身赋值(例如 obj = { name: "查理" };),你是在函数作用域内创建了一个新对象,函数外部的原始对象不会改变。
function reassignObject(obj) {
  obj = { name: "查理" };
  console.log("函数内部: obj.name = " + obj.name); // 输出: 函数内部: obj.name = 查理
}

let myOtherObj = { name: "爱丽丝" };
reassignObject(myOtherObj);
console.log("函数外部: myOtherObj.name = " + myOtherObj.name); // 输出: 函数外部: myOtherObj.name = 爱丽丝

5. 实战示例

让我们看一些向函数传递参数的实用例子。

5.1 计算矩形面积

function calculateArea(length, width) {
  const area = length * width;
  return area;
}

let rectangleLength = 10;
let rectangleWidth = 5;
let area = calculateArea(rectangleLength, rectangleWidth);
console.log("矩形的面积是: " + area); // 输出: 矩形的面积是: 50

5.2 格式化姓名

function formatName(firstName, lastName, format) {
  if (format === "FL") {
    return firstName + " " + lastName;
  } else if (format === "LF") {
    return lastName + ", " + firstName;
  } else {
    return "格式无效";
  }
}

let formattedName1 = formatName("John", "Doe", "FL");
console.log(formattedName1); // 输出: John Doe

let formattedName2 = formatName("Jane", "Smith", "LF");
console.log(formattedName2); // 输出: Smith, Jane

let formattedName3 = formatName("Peter", "Jones", "XYZ");
console.log(formattedName3); // 输出: 格式无效

5.3 更新库存

假设你正在构建一个电商应用。你可以创建一个函数来更新产品的库存:

let inventory = {
  "ProductA": 10,
  "ProductB": 5,
  "ProductC": 20
};

function updateInventory(productName, quantityChange) {
  if (inventory.hasOwnProperty(productName)) {
    inventory[productName] += quantityChange;
  } else {
    console.log("库存中未找到该产品。");
  }
}

updateInventory("ProductA", -3); // 卖出 3 个 ProductA
console.log(inventory["ProductA"]); // 输出: 7

updateInventory("ProductC", 5); // 增加 5 个 ProductC
console.log(inventory["ProductC"]); // 输出: 25

updateInventory("ProductD", 10); // 尝试更新一个不存在的产品
// 输出: 库存中未找到该产品。

在这个例子中,我们将产品名称(字符串)和数量变化(数字)作为参数传递给 updateInventory 函数。该函数直接修改了 inventory 对象,因为对象是按引用传递的。