回调函数(Callbacks)
回调函数是 JavaScript 最初处理异步操作的方式,虽然现在有更好的方法,但理解它很重要。
1. 一句话概括主题
回调函数是作为参数传递给另一个函数的函数,在特定时机被调用,是 JavaScript 最初处理异步操作的方式。
2. 它是什么
想象你要去餐厅吃饭:
同步方式(不现实):
- 你点餐
- 你站在厨房门口等
- 菜做好了,你拿走
- 问题:你什么都做不了,只能干等
回调函数方式(现实):
- 你点餐,拿到一个号码牌
- 你去座位上等(可以做其他事)
- 菜做好了,服务员叫你的号码(调用回调函数)
- 你去取餐
回调函数就像那个”号码牌”,告诉系统”做好了叫我”。
简单理解:回调函数 = 一个”通知函数”,告诉系统”等某个操作完成后,调用这个函数通知我”。
3. 能解决什么问题 + 为什么重要
解决的问题
- 避免阻塞:让程序在等待异步操作时可以做其他事情
- 处理异步结果:在异步操作完成后执行特定代码
- 事件处理:响应用户操作、网络请求等事件
为什么重要
- 历史意义:是 JavaScript 异步编程的起点
- 理解基础:理解回调函数有助于理解 Promise 和 async/await
- 仍然使用:很多 API 仍然使用回调函数(如
setTimeout、事件监听器) - 避免回调地狱:了解回调函数的问题,才能理解为什么需要 Promise
4. 核心知识点拆解
4.1 什么是回调函数
回调函数就是作为参数传递的函数,在特定时机被调用:
// 普通函数
function greet(name) {
console.log('你好,' + name);
}
// 回调函数:作为参数传递
function processUser(name, callback) {
// 做一些处理
const processedName = name.toUpperCase();
// 处理完成后,调用回调函数
callback(processedName);
}
// 使用
processUser('小明', greet);
// 输出:你好,小明4.2 同步回调 vs 异步回调
同步回调
回调函数立即执行:
function syncCallback(callback) {
console.log('开始');
callback(); // 立即执行
console.log('结束');
}
syncCallback(() => {
console.log('回调执行');
});
// 输出:
// 开始
// 回调执行
// 结束异步回调
回调函数在异步操作完成后执行:
function asyncCallback(callback) {
console.log('开始');
setTimeout(() => {
callback(); // 1秒后执行
}, 1000);
console.log('结束');
}
asyncCallback(() => {
console.log('回调执行');
});
// 输出:
// 开始
// 结束
// (1秒后)
// 回调执行4.3 常见的回调函数场景
setTimeout / setInterval
// setTimeout:延迟执行
setTimeout(() => {
console.log('1秒后执行');
}, 1000);
// setInterval:定时执行
setInterval(() => {
console.log('每秒执行一次');
}, 1000);数组方法
const numbers = [1, 2, 3, 4, 5];
// forEach:遍历数组
numbers.forEach((num, index) => {
console.log(`索引 ${index}:${num}`);
});
// map:转换数组
const doubled = numbers.map(num => num * 2);
// filter:过滤数组
const evens = numbers.filter(num => num % 2 === 0);事件监听
// DOM 事件
button.addEventListener('click', (event) => {
console.log('按钮被点击了');
});
// Node.js 事件
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error('读取失败:', error);
} else {
console.log('文件内容:', data);
}
});4.4 错误处理模式
回调函数通常使用”错误优先”模式(Error-First Callback):
function asyncOperation(callback) {
// 模拟异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
// 成功:第一个参数是 null,第二个是结果
callback(null, '操作成功');
} else {
// 失败:第一个参数是错误对象
callback(new Error('操作失败'));
}
}, 1000);
}
// 使用
asyncOperation((error, result) => {
if (error) {
console.error('失败:', error.message);
} else {
console.log('成功:', result);
}
});5. 示例代码(可运行 + 逐行注释)
示例 1:基础回调函数
// 定义一个接受回调函数的函数
function fetchData(callback) {
// 模拟异步操作(比如网络请求)
setTimeout(() => {
const data = {
id: 1,
name: '小明',
email: 'xiaoming@example.com'
};
// 操作完成后,调用回调函数,传入数据
callback(data);
}, 1000); // 1秒后执行
}
// 使用回调函数
fetchData((data) => {
// 这个函数会在数据获取完成后被调用
console.log('获取到的数据:', data);
console.log('用户名:', data.name);
});
console.log('这行会先执行,不会等待 fetchData 完成');
// 输出:
// 这行会先执行,不会等待 fetchData 完成
// (1秒后)
// 获取到的数据: { id: 1, name: '小明', email: 'xiaoming@example.com' }
// 用户名: 小明示例 2:错误处理回调
// 带错误处理的回调函数
function fetchUserData(userId, callback) {
setTimeout(() => {
if (userId > 0) {
// 成功:第一个参数是 null,第二个是数据
callback(null, {
id: userId,
name: '小明',
email: 'xiaoming@example.com'
});
} else {
// 失败:第一个参数是错误对象
callback(new Error('用户ID无效'));
}
}, 1000);
}
// 使用
fetchUserData(1, (error, user) => {
// 检查是否有错误
if (error) {
console.error('获取用户失败:', error.message);
return; // 提前返回,不继续执行
}
// 没有错误,处理数据
console.log('获取用户成功:', user);
console.log('用户名:', user.name);
});
// 测试错误情况
fetchUserData(-1, (error, user) => {
if (error) {
console.error('获取用户失败:', error.message);
} else {
console.log('获取用户成功:', user);
}
});示例 3:回调地狱(Callback Hell)
// 模拟多个异步操作
function login(username, password, callback) {
setTimeout(() => {
if (username === 'admin' && password === '123456') {
callback(null, { token: 'abc123', userId: 1 });
} else {
callback(new Error('登录失败'));
}
}, 500);
}
function getUserInfo(token, callback) {
setTimeout(() => {
if (token) {
callback(null, { name: '管理员', role: 'admin' });
} else {
callback(new Error('无效token'));
}
}, 300);
}
function getPermissions(userId, callback) {
setTimeout(() => {
callback(null, ['read', 'write', 'delete']);
}, 200);
}
// 回调地狱:多层嵌套
login('admin', '123456', (error, auth) => {
if (error) {
console.error('登录失败:', error);
return;
}
getUserInfo(auth.token, (error, userInfo) => {
if (error) {
console.error('获取用户信息失败:', error);
return;
}
getPermissions(auth.userId, (error, permissions) => {
if (error) {
console.error('获取权限失败:', error);
return;
}
// 三层嵌套,代码很难读
console.log('所有操作完成:', {
user: userInfo,
permissions: permissions
});
});
});
});6. 常见错误与踩坑
错误 1:在回调函数外部使用异步结果
// ❌ 错误:在回调外部使用数据
let userData;
fetchData((data) => {
userData = data;
});
console.log(userData); // undefined!因为回调还没执行
// ✅ 正确:在回调内部使用数据
fetchData((data) => {
console.log(data); // 可以正确获取数据
// 在这里处理数据
});为什么错:回调函数是异步执行的,外部代码不会等待它完成。
错误 2:忘记处理错误
// ❌ 错误:没有错误处理
fetchUserData(userId, (error, user) => {
// 如果 error 存在,user 可能是 undefined
console.log(user.name); // 可能报错!
});
// ✅ 正确:先检查错误
fetchUserData(userId, (error, user) => {
if (error) {
console.error('失败:', error);
return;
}
console.log(user.name); // 安全
});错误 3:回调地狱
// ❌ 错误:多层嵌套,难以阅读和维护
operation1((error1, result1) => {
operation2(result1, (error2, result2) => {
operation3(result2, (error3, result3) => {
operation4(result3, (error4, result4) => {
// 代码越来越深,越来越难读
});
});
});
});
// ✅ 正确:使用 Promise 或 async/await
// 详见 [[Promise|Promise]] 和 [[async-await|async/await]]错误 4:回调函数被调用多次
// ❌ 错误:可能调用多次
function fetchData(callback) {
setTimeout(() => {
callback('数据1');
callback('数据2'); // 不应该调用多次
}, 1000);
}
// ✅ 正确:只调用一次
function fetchData(callback) {
let called = false;
setTimeout(() => {
if (!called) {
called = true;
callback('数据');
}
}, 1000);
}7. 实际应用场景
场景 1:定时器
// 延迟执行
setTimeout(() => {
console.log('3秒后执行');
}, 3000);
// 定时执行
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`第 ${count} 次执行`);
if (count >= 5) {
clearInterval(intervalId); // 停止定时器
console.log('定时器已停止');
}
}, 1000);场景 2:DOM 事件
// 点击事件
const button = document.getElementById('myButton');
button.addEventListener('click', (event) => {
console.log('按钮被点击了');
console.log('事件对象:', event);
});
// 表单提交
const form = document.getElementById('myForm');
form.addEventListener('submit', (event) => {
event.preventDefault(); // 阻止默认提交
console.log('表单提交');
});场景 3:数组操作
const numbers = [1, 2, 3, 4, 5];
// 遍历
numbers.forEach((num, index) => {
console.log(`索引 ${index}:${num}`);
});
// 转换
const doubled = numbers.map(num => num * 2);
console.log('翻倍后:', doubled); // [2, 4, 6, 8, 10]
// 过滤
const evens = numbers.filter(num => num % 2 === 0);
console.log('偶数:', evens); // [2, 4]
// 查找
const found = numbers.find(num => num > 3);
console.log('大于3的第一个数:', found); // 48. 给新手的练习题(可立即实践)
基础题:创建一个延迟函数
要求:
- 创建一个
delay函数,接受毫秒数和回调函数 - 在指定时间后调用回调函数
- 测试:延迟 2 秒后输出”延迟完成”
参考代码:
function delay(ms, callback) {
setTimeout(() => {
callback();
}, ms);
}
// 使用
delay(2000, () => {
console.log('延迟完成');
});进阶题:顺序执行多个异步操作
要求:
- 创建三个异步函数,每个延迟 1 秒
- 使用回调函数按顺序执行这三个函数
- 每个函数完成后输出”步骤X完成”
提示:
- 在第一个回调中调用第二个函数
- 在第二个回调中调用第三个函数
9. 用更简单的话再总结一遍(方便复习)
回调函数是什么:
- 就是一个”通知函数”
- 告诉系统”等某个操作完成后,调用这个函数”
核心要点:
- 作为参数传递给另一个函数
- 在特定时机被调用(通常是异步操作完成后)
- 错误优先模式:第一个参数是错误,第二个是结果
- 容易产生回调地狱(多层嵌套)
记住:
- 回调函数 = 异步操作的”通知机制”
- 虽然现在有 Promise 和 async/await,但很多地方还在用
- 理解回调函数有助于理解 Promise
10. 知识体系延伸 & 继续学习方向
相关知识点
继续学习方向
- Promise:学习如何用 Promise 替代回调函数
- async/await:学习更现代的异步写法
- 错误处理:学习异步操作的错误处理
- 实际应用:学习在真实项目中使用异步编程
最后更新:2025