Skip to content

函数参数

理解 JavaScript 函数如何处理参数(parameters)或实参(arguments)对于编写灵活而健壮的代码至关重要。现代 JavaScript 提供了增强的方式来处理参数。

正如您所学到的,函数可以使用形参(parameters)进行声明,形参是函数调用时传入的值的占位符。

function greet(name, greeting) {
// 'name' 和 'greeting' 是形参
console.log(`${greeting}, ${name}!`);
}

函数形参是函数定义中列出的名称(例如,name, greeting)。

函数实参(arguments)是调用函数时提供给函数实际值(例如,greet('Alice', 'Hello'); ‘Alice’ 和 ‘Hello’ 是实参)。

JavaScript 在处理函数实参方面非常灵活:

  • JavaScript 函数定义不为参数指定数据类型。您可以传递任何类型的数据。
  • JavaScript 函数默认不检查传入实参的类型。必要时,由开发者负责验证类型。
  • JavaScript 函数不严格检查接收到的实参数量是否与声明的形参数量匹配。

现代 JavaScript (ES6 及更高版本) 允许您直接在函数签名中为函数参数指定默认值。这是处理缺失实参的首选方式。

如果调用函数时提供的实参少于声明的形参,则没有对应实参的形参将为 undefined。使用默认参数时,它们将采用指定的默认值。

function calculateArea(width, height = 10) {
// 如果没有提供 height,则默认值为 10
return width * height;
}
console.log(calculateArea(5)); // 输出: 50 (height 默认为 10)
console.log(calculateArea(5, 20)); // 输出: 100 (height 是 20)

在此之前,您可能需要手动检查是否为 undefined:

// 传统方式(不简洁)
function myFunction(x, y) {
if (y === undefined) {
y = 0;
}
// ...
}

默认参数使代码更清晰,更易读。

如果调用函数时提供的实参多于声明的形参,则可以访问这些额外的实参。

常规 JavaScript 函数(非箭头函数)拥有一个内置的、类数组对象,称为 arguments。

arguments 对象包含传递给函数的所有实参,无论定义了多少形参。它是“类数组”的,因为它具有 length 属性和索引元素,但它不是真正的数组(如果没有转换,它不具有 forEach、map 等方法)。

function findMax() { // 没有定义形参
let max = -Infinity;
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
console.log(findMax(1, 123, 500, 115, 44, 88)); // 输出: 500

ES6 引入了剩余参数(rest parameters),它提供了一种更简洁的方式来将不定数量的实参作为真正的数组处理。通常推荐使用剩余参数而不是 arguments 对象。

剩余参数语法使用三个点(...),后跟将保存剩余实参的数组名称。

function sumAll(...numbers) {
// 'numbers' 是一个真正的数组
let sum = 0;
for (const num of numbers) {
sum += num;
}
return sum;
// 或者更简洁的方式:return numbers.reduce((acc, current) => acc + current, 0);
}
console.log(sumAll(1, 123, 500, 115, 44, 88)); // 输出: 871

函数可以在剩余参数之前拥有命名参数:

function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
console.log(multiply(2, 1, 2, 3)); // 输出: [2, 4, 6]

注意:剩余参数必须是函数定义中的最后一个参数。箭头函数没有自己的 arguments 对象;请改用剩余参数。

传递实参:按值传递 vs. 按引用传递 (共享)

Section titled “传递实参:按值传递 vs. 按引用传递 (共享)”

理解 JavaScript 如何传递实参是避免意外行为的关键。

当您将基本类型(如 String、Number、Boolean、null、undefined、Symbol、BigInt)传递给函数时,其值会被复制。函数接收的是该值的副本,而不是原始变量的引用。

如果函数修改了参数的值,此更改不会影响函数外部的原始变量。

function changePrimitive(val) {
val = 100; // 修改局部副本
console.log(`Inside function: ${val}`); // 输出: 函数内部: 100
}
let myNumber = 10;
changePrimitive(myNumber);
console.log(`Outside function: ${myNumber}`); // 输出: 函数外部: 10 (原始值未改变)

对象按引用值的拷贝传递 (共享传递):

Section titled “对象按引用值的拷贝传递 (共享传递):”

当您将对象(包括数组和函数)作为实参传递时,传递的值实际上是该对象的引用的副本(内存地址)。这通常被称为“共享传递”。

这意味着函数参数和原始变量指向内存中的同一个对象。

如果函数修改了对象的属性,此更改在函数外部是可见的,因为两个引用指向的是同一个底层对象。

function changeObjectProperty(obj) {
obj.name = "Updated Name"; // 修改原始对象的属性
}
let myPerson = { name: "Original Name" };
changeObjectProperty(myPerson);
console.log(myPerson.name); // 输出: Updated Name

然而,如果函数将参数重新赋值给一个全新的对象,这不会影响函数外部的原始变量。这是因为您更改的是局部的引用副本,而不是原始对象本身。

function reassignObject(obj) {
obj = { name: "New Object" }; // 重新分配局部引用
console.log(`Inside function (reassigned): ${obj.name}`); // 输出: 函数内部(重新分配后): New Object
}
let anotherPerson = { name: "Initial Name" };
reassignObject(anotherPerson);
console.log(`Outside function: ${anotherPerson.name}`); // 输出: 函数外部: Initial Name (原始对象未改变)

理解这种区别对于预测函数如何与数据交互至关重要。