目录
函数回调的定义:
通俗地讲,把一个函数作为参数传给另一个函数,这个函数则称为回调函数。
图解:

在看看严格点的定义:
函数回调就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
下文的函数回调、回调函数是一个意思。
把函数当参数
把函数当做参数的好处是?
假设实现一个计算器的需求,先编写三个功能函数:求两个整数的求最大值,求最小值,求和。
python
def computer(a, b, func):
return func(a, b)
def max(a, b):
return [a, b][a < b]
def min(a, b):
return [a, b][a > b]
def sum(a, b):
return str(int(a) + int(b))
if __name__ == "__main__":
a = input("请输入整数a:")
b = input("请输入整数b:")
res = computer(a, b, max)
print("Max of " + a + " and " + b + " is " + res)
res = computer(a, b, min)
print("Min of " + a + " and " + b + " is " + res)
res = computer(a, b, sum)
print("Sum of " + a + " and " + b + " is " + res)
text
请输入整数a:2
请输入整数b:3
Max of 2 and 3 is 3
Min of 2 and 3 is 2
Sum of 2 and 3 is 5
但是这个包将作为SDK给别人使用的话,是不知道别人想搭配什么功能函数的。那么可以将这三个函数都作为实参,让用户自由传入,这样就增加了编程的灵活性。
在调用max、min、sum时,这三个函数就是此处的回调函数。(回调函数和普通函数在定义的时候没有什么区别,只有在调用时才看出来是不是回调函数,正常调用就是普通函数,作为一个函数的参数在需要的时候分情况调用,就是回调函数。)
可以异步的函数
当然回调函数还有一个更大的作用,就是可以结合上下文 做异步,好处是异步不阻塞。
需要说明一下,回调的异步性并非来自函数本身,而是由调用它的API或操作(如setTimeout、I/O、事件监听)决定的。这种设计使得程序能在等待耗时操作时不阻塞主线程,从而提升效率和用户体验。理解事件循环和任务队列机制是掌握异步编程的关键。
异步不是本文的核心,这里只看看回调怎么结合异步实现一些超出同步编程的效果就好。
异步在前端编程中很常用,下面通过一个 前端开发场景 来展示异步回调如何解决同步代码无法处理的问题:避免界面冻结,同时执行耗时任务。
案例背景:模拟文件上传
假设我们正在开发一个网页,用户点击按钮后需要:
- 上传一个大文件到服务器(耗时操作)。
- 上传完成后显示"上传成功"。
- 同时,用户在上传过程中可以继续操作页面(比如输入文字、点击其他按钮)。
同步代码的问题
如果用同步代码实现文件上传,会阻塞主线程,导致界面完全卡死,用户无法进行任何操作:
javascript
// 同步上传函数(假设存在同步的 uploadSync API)
function uploadSync(file) {
// 模拟耗时操作(假设上传需要3秒)
const start = Date.now();
while (Date.now() - start < 3000) {} // 同步阻塞3秒
return "上传成功";
}
// 点击按钮触发上传
document.getElementById("uploadBtn").addEventListener("click", () => {
console.log("开始上传...");
const result = uploadSync("bigfile.zip"); // 同步调用,阻塞主线程3秒
console.log(result);
document.getElementById("status").textContent = result;
});
// 用户尝试在上传过程中输入文字,但界面会卡住3秒!
问题:
-
上传期间,用户无法在输入框打字,所有UI操作被冻结。
-
控制台输出顺序是:
开始上传... (3秒后) 上传成功
异步回调解决方案
改用异步回调,释放主线程,让用户在上传过程中继续操作页面:
javascript
// 异步上传函数(使用回调)
function uploadAsync(file, callback) {
console.log("开始上传...");
// 使用 setTimeout 模拟异步上传(如真实的 fetch 或 XMLHttpRequest)
setTimeout(() => {
const result = "上传成功";
callback(result); // 上传完成后调用回调
}, 3000);
}
// 点击按钮触发上传
document.getElementById("uploadBtn").addEventListener("click", () => {
uploadAsync("bigfile.zip", (result) => {
console.log(result);
document.getElementById("status").textContent = result;
});
});
// 用户可以在上传过程中正常输入文字!
关键区别:
-
上传期间,用户可以在输入框自由输入,界面保持响应。
-
控制台输出顺序是:
开始上传... (立即输出,不阻塞) (3秒后) 上传成功
流程图解
[用户点击上传按钮]
│
├─ 主线程执行:调用 uploadAsync
│ │
│ ├─ 1. 输出 "开始上传..."
│ │
│ ├─ 2. 启动异步操作(setTimeout 3秒)
│ │ │
│ │ └─ (3秒后)执行回调:更新界面状态
│ │
│ └─ 3. 函数立即返回,主线程空闲
│
├─ 用户立即可以操作页面(输入文字、点击其他按钮)
│
└─ 3秒后,回调触发,更新界面
为什么异步回调解决了同步无法处理的问题?
-
非阻塞主线程:
- 同步代码会独占主线程,导致浏览器无法处理用户输入、动画渲染等任务。
- 异步回调将耗时任务交给浏览器底层API(如网络线程、定时器线程),主线程继续响应用户操作。
-
保持用户体验:
- 用户在上传文件时,仍可以与其他UI元素交互(如填写表单、切换标签页)。
-
真实场景应用:
- 所有Web应用的网络请求(如AJAX、Fetch API)、文件读写(Node.js)、数据库操作都必须使用异步,否则会导致服务完全卡死。
实际开发中会用到的异步回调
真实项目中,异步回调常用于:
javascript
// 1. 网络请求
fetch("/api/data")
.then(response => response.json())
.then(data => console.log(data));
// 2. 定时任务
setTimeout(() => console.log("延时操作"), 1000);
// 3. 用户事件监听
document.getElementById("button").addEventListener("click", () => {
console.log("按钮被点击");
});
// 4. Node.js 文件读取
const fs = require("fs");
fs.readFile("file.txt", "utf8", (err, data) => {
if (err) throw err;
console.log(data);
});
以上场景一般也都是 回调函数 + 异步实现。
总结
- 同步代码的问题:阻塞主线程,导致界面冻结、用户体验极差。
- 异步回调的优势 :
- 主线程保持响应,用户可以继续操作。
- 充分利用硬件资源(如多线程、非阻塞I/O)。
- 适用于所有耗时操作(网络、I/O、复杂计算)。
这也是现代Web开发中,异步回调(及其衍生技术如Promise、Async/Await)是必须掌握的核心概念!
有兴趣的同学,可以更深入地去了解系统底层都做了什么,才能达成异步效果。 Bye !