深入理解 JavaScript 函数:形参、实参、回调函数与函数调用

237 阅读6分钟

在现代 JavaScript 开发中,函数是核心组件之一。理解函数的形参、实参、函数调用、以及回调函数等概念,对于编写高效、可维护的代码至关重要。本文将全面深入地介绍这些概念,并通过多个示例和练习题帮助你掌握这些内容。

一、什么是函数?

1.1 函数的定义

函数是一组可以重复执行的代码块,通常用于完成特定的任务。函数在 JavaScript 中是“一等公民”,这意味着函数可以像任何其他值一样被赋值给变量、传递给其他函数,甚至可以作为函数的返回值。

1.2 函数的定义方式

1.2.1 函数声明

function 函数名(参数1, 参数2, ...) {
  // 函数体
  return 返回值;
}
function add(a, b) {
  return a + b;
}

1.2.2 函数表达式

const 函数名 = function(参数1, 参数2, ...) {
  // 函数体
  return 返回值;
};
const multiply = function(a, b) {
  return a * b;
};

1.2.3 箭头函数(ES6)

const 函数名 = (参数1, 参数2, ...) => {
  // 函数体
  return 返回值;
};
const divide = (a, b) => a / b;

二、形参(形式参数)

2.1 形参的作用

形参是函数在定义时声明的变量,用于接收函数调用时传递的值。形参仅在函数体内有效,函数外部无法访问。

function greet(name) {
  console.log('Hello, ' + name + '!');
}

在这个例子中,name 是形参,它是一个占位符,用来接收函数调用时的实际值。

2.2 形参的默认值

JavaScript 允许为形参设置默认值。如果调用函数时没有提供对应的实参,形参将使用默认值。

function greet(name = 'Guest') {
  console.log('Hello, ' + name + '!');
}

greet(); // 输出: Hello, Guest!
greet('Alice'); // 输出: Hello, Alice!

当调用 greet 函数时,如果没有传递 name 的实参,形参 name 将使用默认值 'Guest'

三、实参(实际参数)

3.1 实参的作用

实参是在函数调用时传递给函数的实际值,这些值将赋值给函数的形参,并在函数内部使用。

greet('Alice');

在这里,'Alice' 是实参,它被传递给了 greet 函数中的 name 形参。

3.2 多个实参与参数传递

函数可以接收多个实参,并按照定义时的顺序将这些实参传递给对应的形参。

function calculate(a, b, c) {
  return a + b * c;
}

let result = calculate(2, 3, 4);
console.log(result); // 输出: 14

在这个例子中,实参 234 被传递给了形参 abc,然后在函数体内进行运算。

3.3 参数的传递方式

3.3.1 值传递

对于基本数据类型(如数字、字符串、布尔值),参数是按值传递的。在函数内部修改形参不会影响外部的实参。

function changeValue(x) {
  x = 10;
}

let y = 5;
changeValue(y);
console.log(y); // 输出: 5

3.3.2 引用传递

对于复杂数据类型(如对象、数组),参数是按引用传递的。这意味着在函数内部对形参的修改会影响外部的实参。

function changeObject(obj) {
  obj.name = 'Alice';
}

let person = { name: 'Bob' };
changeObject(person);
console.log(person.name); // 输出: Alice

四、回调函数

4.1 什么是回调函数?

回调函数是一个被作为参数传递给另一个函数的函数。通常,回调函数在某个事件发生或某个操作完成后被执行。

function doSomething(callback) {
  console.log('Doing something...');
  callback();
}

function sayHello() {
  console.log('Hello!');
}

doSomething(sayHello);
// 输出: Doing something...
// 输出: Hello!

在这个例子中,sayHello 函数被作为回调函数传递给 doSomething 函数,并在 doSomething 执行完某些操作后调用。

4.2 回调函数的使用场景

回调函数在处理异步操作时非常常见,例如处理用户事件、网络请求、定时器等。

4.2.1 事件处理回调

document.getElementById('myButton').addEventListener('click', function() {
  alert('Button clicked!');
});

在这个例子中,匿名函数被作为回调传递给 addEventListener,并在按钮被点击时执行。

4.2.2 异步操作回调

setTimeout(function() {
  console.log('This message appears after 2 seconds');
}, 2000);

这里,setTimeout 接收的回调函数将在 2 秒后被执行。

4.3 匿名回调函数

在很多情况下,回调函数不需要有名称,这时我们可以使用匿名函数来作为回调。

function processNumbers(numbers, callback) {
  for (let i = 0; i < numbers.length; i++) {
    numbers[i] = callback(numbers[i]);
  }
  return numbers;
}

let doubled = processNumbers([1, 2, 3], function(num) {
  return num * 2;
});

console.log(doubled); // 输出: [2, 4, 6]

在这个例子中,匿名回调函数用于将数组中的每个元素乘以 2。

五、函数返回值

5.1 返回值的作用

函数可以通过 return 语句将结果返回给调用者。如果函数没有 return 语句,函数的返回值将是 undefined

function subtract(a, b) {
  return a - b;
}

let result = subtract(10, 3);
console.log(result); // 输出: 7

5.2 早期返回

有时候,我们希望在某些条件下立即退出函数并返回一个值。这可以通过 return 语句实现。

function checkPositive(number) {
  if (number < 0) {
    return 'Negative number';
  }
  return 'Positive number';
}

console.log(checkPositive(-5)); // 输出: Negative number

六、函数作为参数传递

6.1 函数可以作为参数

在 JavaScript 中,函数可以作为参数传递给其他函数。这种做法使得代码更加灵活和可复用。

function operation(a, b, func) {
  return func(a, b);
}

let sum = operation(5, 3, function(x, y) {
  return x + y;
});

console.log(sum); // 输出: 8

在这个例子中,operation 函数接受了两个数字和一个函数作为参数,最后调用了传入的函数 func 来计算结果。

6.2 高阶函数

高阶函数是指可以接受函数作为参数或返回另一个函数的函数。回调函数和函数作为参数传递都属于高阶函数的应用。

6.2.1 map 函数

let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(function(num) {
  return num * 2;
});

console.log(doubled); // 输出: [2, 4, 6, 8, 10]

在这个例子中,map 函数就是一个高阶函数,它接受一个回调函数,并将其应用到数组的每一个元素上。

七、练习题

为了巩固学习内容,以下是一些练习题,帮助你更好地理解函数、回调函数及函数调用等概念。

练习 1:创建一个函数 square,接受一个参数,返回它的平方值。

function square(x) {
  // 你的代码
}
console.log(square(4)); // 16

练习 2:编写一个函数 operate,接受两个数字和一个操作函数作为参数,根据传入的操作函数返回计算结果。

function operate(a, b, operation) {
  // 你的代码
}
console.log(operate(10, 5, function(x, y) { return x - y; })); // 5
console.log(operate(2, 3, function(x, y) { return x * y; })); // 6

练习 3:编写一个高阶函数 repeat,它接收一个函数 fn 和一个数字 n,然后执行 fn 函数 n 次。

function repeat(fn, n) {
  // 你的代码
}
repeat(function() { console.log('Hello'); }, 3);
// 输出:
// Hello
// Hello
// Hello

练习 4:创建一个函数 filterArray,接受一个数组和一个回调函数,返回只包含满足回调函数条件的元素的新数组。

function filterArray(arr, callback) {
  // 你的代码
}
console.log(filterArray([1, 2, 3, 4, 5], function(num) {
  return num > 2;
})); // [3, 4, 5]

练习 5:编写一个函数 delay,接受一个回调函数 fn 和一个延迟时间 ms,在指定时间后调用该回调函数。

function delay(fn, ms) {
  // 你的代码
}
delay(function() { console.log('Executed after delay'); }, 2000);
// 2秒后输出: Executed after delay

八、总结

本文全面介绍了 JavaScript 中函数的基础知识,包括函数的定义、形参与实参的作用、回调函数、函数作为参数传递的高级用法等。通过这些深入的讲解和练习题,你应该对函数的各种用法有了更加全面的理解。

通过实践这些练习,你将能够更灵活地在项目中应用这些知识。如果你有任何问题或想法,欢迎在评论区讨论!