JavaScript函数参数

在本章节中,我们将学习JavaScript函数参数的相关知识,包括参数的类型、默认值、剩余参数、展开运算符等内容。

1. 函数参数的基本概念

函数参数是函数定义时声明的变量,用于接收函数调用时传递的值。函数参数可以是零个或多个。

// 函数定义时的参数称为形参(形式参数)
function add(a, b) {
    // a和b是形参
    return a + b;
}

// 函数调用时传递的值称为实参(实际参数)
let result = add(5, 3); // 5和3是实参

2. 参数的类型

在JavaScript中,函数参数没有类型限制,可以传递任何类型的值:

// 传递数字
function add(a, b) {
    return a + b;
}

let result1 = add(5, 3); // 8

// 传递字符串
let result2 = add("Hello", " World"); // "Hello World"

// 传递布尔值
function isBothTrue(a, b) {
    return a && b;
}

let result3 = isBothTrue(true, false); // false

// 传递对象
function getFullName(person) {
    return person.firstName + " " + person.lastName;
}

let person = { firstName: "张", lastName: "三" };
let result4 = getFullName(person); // "张三"

// 传递数组
function getFirstElement(arr) {
    return arr[0];
}

let numbers = [1, 2, 3];
let result5 = getFirstElement(numbers); // 1

// 传递函数
function calculate(a, b, operator) {
    return operator(a, b);
}

let result6 = calculate(5, 3, (x, y) => x * y); // 15

3. 参数的默认值

ES6(2015)引入了参数默认值的特性,允许为函数参数设置默认值,当调用函数时不传递该参数或传递undefined时,使用默认值。

3.1 基本默认值

// 参数默认值示例
function greet(name = "陌生人") {
    console.log(`你好,${name}!`);
}

greet(); // 你好,陌生人!
greet("张三"); // 你好,张三!
greet(undefined); // 你好,陌生人!

greet(null); // 你好,null!(注意:null不会使用默认值)

3.2 多个参数的默认值

// 多个参数的默认值
function add(a = 0, b = 0) {
    return a + b;
}

console.log(add()); // 0
console.log(add(5)); // 5
console.log(add(5, 3)); // 8
console.log(add(undefined, 3)); // 3

3.3 表达式作为默认值

默认值可以是表达式,甚至是函数调用:

// 表达式作为默认值
function getCurrentYear() {
    return new Date().getFullYear();
}

function createPerson(name, birthYear, currentYear = getCurrentYear()) {
    let age = currentYear - birthYear;
    return { name, birthYear, age };
}

let person1 = createPerson("张三", 2000); // { name: "张三", birthYear: 2000, age: 26 }(假设当前年份是2026)
let person2 = createPerson("李四", 1995, 2020); // { name: "李四", birthYear: 1995, age: 25 }

// 函数调用作为默认值
function randomNumber() {
    return Math.random();
}

function generateId(prefix, suffix = randomNumber()) {
    return prefix + suffix;
}

console.log(generateId("id-")); // id-0.123456789
console.log(generateId("id-", "123")); // id-123

3.4 默认值的位置

带有默认值的参数应该放在参数列表的末尾:

// 不推荐:带有默认值的参数放在前面
function greet(name = "陌生人", message) {
    console.log(`${message},${name}!`);
}

greet(undefined, "你好"); // 你好,陌生人!
greet("张三"); // undefined,张三!(message为undefined)

// 推荐:带有默认值的参数放在后面
function greet(message, name = "陌生人") {
    console.log(`${message},${name}!`);
}

greet("你好"); // 你好,陌生人!
greet("你好", "张三"); // 你好,张三!

4. 剩余参数

ES6(2015)引入了剩余参数的特性,使用...语法将剩余的实参收集到一个数组中。

4.1 剩余参数的基本使用

// 剩余参数示例
function sum(...numbers) {
    return numbers.reduce((total, number) => total + number, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0

4.2 剩余参数与普通参数结合使用

剩余参数必须是参数列表的最后一个参数:

// 剩余参数与普通参数结合使用
function multiply(multiplier, ...numbers) {
    return numbers.map(number => number * multiplier);
}

console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]
console.log(multiply(3)); // []

// 错误示例:剩余参数不是最后一个参数
// function test(...numbers, multiplier) {
//     // 代码
// }

4.3 剩余参数与arguments对象的区别

剩余参数与arguments对象类似,但有以下区别:

  1. 剩余参数是一个真正的数组,可以使用数组方法;arguments是一个类数组对象,需要转换为数组才能使用数组方法。
  2. 剩余参数只包含那些没有对应形参的实参;arguments包含所有实参。
  3. 剩余参数是ES6引入的新特性;arguments是JavaScript的传统特性。
// 剩余参数示例
function test1(...args) {
    console.log(args); // [1, 2, 3](数组)
    console.log(args instanceof Array); // true
    console.log(args.reduce((total, num) => total + num, 0)); // 6
}

test1(1, 2, 3);

// arguments示例
function test2() {
    console.log(arguments); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ](类数组对象)
    console.log(arguments instanceof Array); // false
    console.log(Array.from(arguments).reduce((total, num) => total + num, 0)); // 6
}

test2(1, 2, 3);

5. 展开运算符

展开运算符(Spread Operator)也是ES6引入的新特性,使用...语法,它与剩余参数的语法相同,但作用相反:

  • 剩余参数:将多个实参收集到一个数组中
  • 展开运算符:将数组或对象展开为多个实参

5.1 展开数组作为函数参数

// 展开数组作为函数参数
let numbers = [1, 2, 3, 4, 5];

console.log(Math.max(...numbers)); // 5
console.log(Math.min(...numbers)); // 1

// 合并数组
let moreNumbers = [6, 7, 8];
let allNumbers = [...numbers, ...moreNumbers]; // [1, 2, 3, 4, 5, 6, 7, 8]

// 函数调用时展开数组
function add(a, b, c) {
    return a + b + c;
}

let nums = [1, 2, 3];
console.log(add(...nums)); // 6

5.2 展开对象作为函数参数

// 展开对象作为函数参数
function createPerson(name, age, city) {
    return {
        name,
        age,
        city
    };
}

let personInfo = { name: "张三", age: 18 };
let cityInfo = { city: "北京" };

let person = createPerson(...Object.values(personInfo), cityInfo.city); // { name: "张三", age: 18, city: "北京" }

// 合并对象
let person1 = { name: "张三", age: 18 };
let person2 = { city: "北京", ...person1 };
console.log(person2); // { city: "北京", name: "张三", age: 18 }

6. 解构赋值作为参数

ES6引入了解构赋值的特性,可以将数组或对象解构为多个变量。解构赋值也可以用于函数参数:

6.1 数组解构作为参数

// 数组解构作为参数
function sum([a, b, c]) {
    return a + b + c;
}

let numbers = [1, 2, 3];
console.log(sum(numbers)); // 6

// 带有默认值的数组解构参数
function getFirstTwo([first = 0, second = 0]) {
    return [first, second];
}

console.log(getFirstTwo([5, 10])); // [5, 10]
console.log(getFirstTwo([])); // [0, 0]

6.2 对象解构作为参数

// 对象解构作为参数
function greet({ name, age }) {
    console.log(`你好,我叫${name},今年${age}岁。`);
}

let person = { name: "张三", age: 18 };
greet(person); // 你好,我叫张三,今年18岁。

// 带有默认值的对象解构参数
function createUser({ name = "匿名", age = 0, city = "未知" }) {
    return {
        name,
        age,
        city
    };
}

console.log(createUser({ name: "李四", city: "上海" })); // { name: "李四", age: 0, city: "上海" }
console.log(createUser({})); // { name: "匿名", age: 0, city: "未知" }

// 为整个参数设置默认值
function createUser2({ name = "匿名", age = 0 } = {}) {
    return {
        name,
        age
    };
}

console.log(createUser2()); // { name: "匿名", age: 0 }(不传递参数时使用默认值)

7. arguments对象

arguments是一个类数组对象,包含了函数调用时传递的所有实参。arguments对象是函数内部的一个局部变量,只能在函数内部访问。

7.1 arguments对象的基本使用

// arguments对象示例
function test() {
    console.log(arguments); // Arguments对象
    console.log(arguments.length); // 实参的数量
    console.log(arguments[0]); // 第一个实参
    console.log(arguments[1]); // 第二个实参
}

test(1, 2, 3); // Arguments(3) [1, 2, 3, ...]

7.2 arguments对象的方法

arguments对象有以下方法:

  • arguments.callee:指向当前正在执行的函数(不推荐使用,在严格模式下被禁用)
  • arguments.caller:指向调用当前函数的函数(不推荐使用,在严格模式下被禁用)
  • arguments.length:返回实参的数量
  • arguments[Symbol.iterator]:返回一个迭代器,可以使用for...of循环遍历
// 使用arguments对象遍历实参
function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// 使用for...of循环遍历arguments对象
function test() {
    for (let arg of arguments) {
        console.log(arg);
    }
}

test(1, 2, 3); // 1 2 3

7.3 arguments对象与剩余参数的结合使用

// arguments对象与剩余参数的结合使用
function test(...args) {
    console.log("剩余参数:", args);
    console.log("arguments对象:", arguments);
}

test(1, 2, 3);

8. 参数的传递方式

在JavaScript中,函数参数的传递方式是按值传递(Pass by Value):

  1. 对于基本数据类型(数字、字符串、布尔值、undefined、null、Symbol、BigInt),传递的是值的副本。
  2. 对于引用数据类型(对象、数组、函数),传递的是引用地址的副本。

8.1 基本数据类型的传递

// 基本数据类型的传递
function changeValue(num) {
    num = 10;
    console.log("函数内部的num:", num); // 10
}

let x = 5;
changeValue(x);
console.log("函数外部的x:", x); // 5(x的值没有改变)

8.2 引用数据类型的传递

// 引用数据类型的传递
function changeObject(person) {
    person.name = "李四";
    console.log("函数内部的person:", person); // { name: "李四", age: 18 }
}

let person = { name: "张三", age: 18 };
changeObject(person);
console.log("函数外部的person:", person); // { name: "李四", age: 18 }(person的属性被改变)

// 注意:如果在函数内部重新赋值引用类型参数,外部的引用不会改变
function replaceObject(person) {
    person = { name: "王五", age: 20 };
    console.log("函数内部的person:", person); // { name: "王五", age: 20 }
}

replaceObject(person);
console.log("函数外部的person:", person); // { name: "李四", age: 18 }(person的引用没有改变)

9. 函数参数的最佳实践

9.1 限制参数数量

函数参数数量不宜过多,一般不超过3-5个。如果需要传递多个参数,建议使用对象或数组:

// 不推荐:参数数量过多
function createUser(name, age, city, email, phone) {
    // 代码
}

// 推荐:使用对象传递多个参数
function createUser(userInfo) {
    const { name, age, city, email, phone } = userInfo;
    // 代码
}

let userInfo = {
    name: "张三",
    age: 18,
    city: "北京",
    email: "zhangsan@example.com",
    phone: "13800138000"
};

createUser(userInfo);

9.2 使用默认值

为可选参数设置默认值,提高函数的健壮性:

// 推荐:为可选参数设置默认值
function fetchData(url, options = { method: "GET", headers: {} }) {
    // 代码
}

fetchData("https://api.example.com/data");
fetchData("https://api.example.com/data", { method: "POST" });

9.3 使用剩余参数处理可变数量的参数

使用剩余参数处理可变数量的参数,提高函数的灵活性:

// 推荐:使用剩余参数处理可变数量的参数
function sum(...numbers) {
    return numbers.reduce((total, number) => total + number, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

9.4 使用解构赋值简化参数访问

使用解构赋值简化对象或数组参数的访问:

// 推荐:使用对象解构赋值简化参数访问
function greet({ name, age }) {
    console.log(`你好,我叫${name},今年${age}岁。`);
}

let person = { name: "张三", age: 18 };
greet(person);

10. 常见问题解答

Q: 什么是形参和实参?

A: 函数定义时的参数称为形参(形式参数),函数调用时传递的值称为实参(实际参数)。

Q: JavaScript函数参数的传递方式是什么?

A: JavaScript函数参数的传递方式是按值传递:对于基本数据类型,传递的是值的副本;对于引用数据类型,传递的是引用地址的副本。

Q: 什么是参数默认值?

A: 参数默认值是ES6引入的特性,允许为函数参数设置默认值,当调用函数时不传递该参数或传递undefined时,使用默认值。

Q: 什么是剩余参数?

A: 剩余参数是ES6引入的特性,使用...语法将剩余的实参收集到一个数组中。

Q: 剩余参数和arguments对象有什么区别?

A: 剩余参数是一个真正的数组,可以使用数组方法;arguments是一个类数组对象,需要转换为数组才能使用数组方法。剩余参数只包含那些没有对应形参的实参;arguments包含所有实参。

Q: 什么是展开运算符?

A: 展开运算符是ES6引入的特性,使用...语法将数组或对象展开为多个实参。

Q: 什么是解构赋值作为参数?

A: 解构赋值作为参数是指在函数定义时,使用数组或对象解构语法将参数解构为多个变量。

11. 练习项目

  1. 创建一个HTML文件,包含以下内容:

    • 一个输入框,用于输入多个数字(用逗号分隔)
    • 一个按钮,用于计算
    • 一个显示结果的区域
  2. 使用JavaScript实现以下功能:

    • 定义一个函数,接受多个数字作为参数
    • 计算这些数字的和、平均值、最大值和最小值
    • 使用剩余参数处理可变数量的参数
    • 使用展开运算符将输入的字符串转换为数字数组
    • 在结果区域显示计算结果
  3. 测试不同的输入值,确保计算结果正确

12. 小结

  • 函数参数是函数定义时声明的变量,用于接收函数调用时传递的值
  • JavaScript函数参数没有类型限制,可以传递任何类型的值
  • ES6引入了参数默认值的特性,允许为函数参数设置默认值
  • 剩余参数使用...语法将剩余的实参收集到一个数组中
  • 展开运算符使用...语法将数组或对象展开为多个实参
  • 解构赋值可以用于函数参数,简化对象或数组参数的访问
  • arguments是一个类数组对象,包含了函数调用时传递的所有实参
  • JavaScript函数参数的传递方式是按值传递:对于基本数据类型,传递的是值的副本;对于引用数据类型,传递的是引用地址的副本
  • 函数参数的最佳实践包括:限制参数数量、使用默认值、使用剩余参数处理可变数量的参数、使用解构赋值简化参数访问

在下一章节中,我们将学习JavaScript函数返回值的相关知识。

« 上一篇 JavaScript函数 下一篇 » JavaScript函数返回值