什么是回调函数?
简单来说: 回调函数就是一个函数,它作为参数传递给另一个函数,在这个函数完成某些操作后再调用(回调)这个传递进来的函数。
例子 1:订餐场景
假设你给餐厅打电话订餐(打电话相当于调用函数),你告诉餐厅:
- 当饭做好后,请打电话通知我(告诉他们回调一个函数)。
- 等饭做好了,他们会回调你给他们的号码(即函数),然后你就接到通知。
// 模拟订餐的函数
function orderFood(callback) {
console.log("订单已提交,等待饭做好...");
setTimeout(() => {
// 3秒钟后通知你
callback("饭已做好,快来取吧!");
}, 3000);
}
// 使用函数时,传递一个回调函数
orderFood((message) => {
console.log(message); // 打印通知:饭已做好,快来取吧!
});
代码说明:
orderFood
是订餐函数,你告诉餐厅完成后给你通知。callback
是一个回调函数,你传递给orderFood
。- 当餐厅完成了任务(饭做好了),调用
callback
给你发送消息。
例子 2:洗衣服的流程
假设你用洗衣机洗衣服,洗衣服这个过程需要一段时间。等洗好后,洗衣机会发出“滴滴”的声音通知你(回调函数)。
function doLaundry(callback) {
console.log("开始洗衣服...");
setTimeout(() => {
// 模拟 3 秒钟的洗衣过程
console.log("衣服洗好了!");
callback(); // 回调通知你
}, 3000);
}
// 使用时传递回调函数
doLaundry(() => {
console.log("把衣服晾起来!");
});
运行流程:
- 先打印:
开始洗衣服...
。 - 3 秒后,打印:
衣服洗好了!
。 - 然后调用回调函数,打印:
把衣服晾起来!
。
回调函数的好处
- 异步操作:当某些操作需要耗时时(比如网络请求、读写文件等),回调函数可以让程序继续运行,不用等待操作完成。
- 灵活性:你可以传入不同的回调函数,来定义操作完成后的不同行为。
例子 3:多种回调
假设你在洗衣服时,不同情况下想做不同的事情。
function doLaundry(callback) {
console.log("开始洗衣服...");
setTimeout(() => {
console.log("衣服洗好了!");
callback(); // 回调通知你
}, 3000);
}
// 情况 1:晾衣服
doLaundry(() => {
console.log("晾衣服!");
});
// 情况 2:直接穿上(假设是烘干机洗的)
doLaundry(() => {
console.log("直接穿衣服!");
});
结果:
- 第一个调用会输出:
开始洗衣服...
->衣服洗好了!
->晾衣服!
- 第二个调用会输出:
开始洗衣服...
->衣服洗好了!
->直接穿衣服!
回调函数与同步代码对比
普通同步代码
function sayHello() {
console.log("你好!");
}
sayHello();
console.log("程序结束");
输出:
你好!
程序结束
异步回调代码
function sayHelloAsync(callback) {
setTimeout(() => {
console.log("你好!");
callback();
}, 1000);
}
sayHelloAsync(() => {
console.log("程序结束");
});
输出:
你好!(延迟 1 秒打印)
程序结束
常见的回调函数场景
-
事件监听
const button = document.getElementById("myButton"); button.addEventListener("click", () => { console.log("按钮被点击了!"); });
-
定时器
setTimeout(() => { console.log("延迟 2 秒后执行"); }, 2000);
-
数组操作(如
map
、forEach
)
const numbers = [1, 2, 3];
numbers.forEach((num) => console.log(num * 2)); // 输出 2, 4, 6
-
数组
numbers
- 定义了一个数组
numbers
,它包含 3 个数字:1
,2
,3
。
- 定义了一个数组
-
forEach
方法forEach
是数组的一个方法,用来遍历数组中的每一项,并对每一项执行一个回调函数。
-
回调函数
(num) => console.log(num * 2)
- 这是一个箭头函数。
num
是forEach
方法自动传入的参数,代表当前遍历到的数组项。- 每次遍历时,
console.log(num * 2)
会将当前的num
乘以 2,然后输出到控制台。
运行流程
forEach
会对数组中的每个元素执行回调函数,具体的执行流程如下:
第一次遍历
- 当前元素是
1
,回调函数console.log(num * 2)
中的num
就是1
。 - 输出:
1 * 2 = 2
。
第二次遍历
- 当前元素是
2
,回调函数中的num
是2
。 - 输出:
2 * 2 = 4
。
第三次遍历
- 当前元素是
3
,回调函数中的num
是3
。 - 输出:
3 * 2 = 6
。
最终结果:
2
4
6
简化理解
你可以把 forEach
理解成一个自动帮你遍历数组的循环,每遍历一个元素,就执行一次你传给它的函数。
手动实现 forEach
的逻辑(模拟)
我们用普通的 for
循环来实现:
const numbers = [1, 2, 3];
for (let i = 0; i < numbers.length; i++) {
const num = numbers[i];
console.log(num * 2);
}
运行结果和 forEach
是一样的。
换个更简单的例子:理解回调和遍历
例子 1:输出所有数组元素
const numbers = [1, 2, 3];
numbers.forEach((num) => console.log(num));
运行结果:
1
2
3
这里的回调函数 (num) => console.log(num)
只是简单地把数组中的每个元素打印出来。
例子 2:将每个元素加 5 后打印
const numbers = [1, 2, 3];
numbers.forEach((num) => console.log(num + 5));
运行结果:
6
7
8
每次遍历时,num
会被替换成当前的数组元素,然后加上 5。
和 map
的区别
如果你想对数组中的每一项进行操作并返回一个新数组,应该使用 map
:
例子:用 map
返回一个新数组
const numbers = [1, 2, 3];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6]
这里的 map
会将每个元素乘以 2,并返回一个包含结果的新数组,而 forEach
不会返回新数组,只是执行操作。