回调函数是作为参数传入并由系统在异步操作完成后自动调用的普通函数,其核心在于调用时机与作用域理解,而非语法本身。
JavaScript 异步编程不是“让代码跑得更快”,而是“不让代码卡住页面”。回调函数就是你提前写好、但不立刻执行的那部分逻辑——它被塞进另一个函数里,等某个耗时操作(比如网络请求、定时器)真正做完后,再由系统自动调用。
它就是一个普通函数,只是被当作参数传给另一个函数,并在特定时机被调用。关键不在“怎么写”,而在“谁调用、什么时候调用”。
setTimeout 的第一个参数必须是函数,这就是最典型的回调函数addEventListener 的第二个参数也是回调函数,用户点击时才触发setTimeout(myFunction(), 1000) —— 这会立即执行 myFunction,返回值(通常是 undefined)被传进去,导致报错或静默失败setTimeout(myFunction, 1000) 或 setTimeout(() => { ... }, 1000)
因为 JavaScript 是单线程 + 事件循环模型。主线程不会等异步操作完成,而是继续往下跑。回调函数会被放入任务队列,等主线程空了才执行。
console.log('1. 开始');
setTimeout(() => {
console.log('2. 两秒后执行');
}, 2000);
console.log('3. 立即执行');
输出一定是 1 → 3 → 2。这不是 bug,是设计使然——否则一个 2 秒的请求会让整个页面冻结 2 秒。
不是语法写错,而是对“执行时机”和“作用域”的误判。
for (var i = 0; i console.log(i), 0);
} 输出全是 3,因为 var 声明的 i 是函数作用域,循环结束时 i === 3;改用 let 或立即执行函数可解决fs.readFile)采用“错误优先”模式,回调第一个参数是 err,必须先检查 if (err) { ... }
setTimeout 或 AJAX 回调嵌套,就该考虑用 Promise 或 async/await,否则连自己都看不懂执行流回调函数本身没有魔法,它的难点从来不在“怎么写”,而在于你是否清楚“它会在哪条线程、哪个时刻、带着什么变量值被调用”。一旦开始依赖多个异步结果的顺序或组合,光靠回调就容易失控——这时候不是回调错了,是它已经完成了自己的历史使命。