写代码时,经常会遇到需要“事情办完后通知我”的场景。比如发个网络请求,等结果回来再处理数据;或者点个按钮,等用户操作后再执行一段逻辑。这种“事后通知”的机制,通常靠回调函数实现。
但问题来了:光调用一个函数不够用,我们往往还得把一些上下文信息一起传进去。这时候就得给回调函数传参。
直接传参的误区
新手常犯的一个错误是这样写:
setTimeout(console.log('Hello'), 1000);
本意是想一秒钟后打印 Hello,结果页面一加载就立刻执行了。因为这里不是在传函数,而是在执行函数。正确做法是传一个函数引用。
使用匿名函数包装
最简单直接的方式,是用一个匿名函数把调用包一层:
setTimeout(function() {
console.log('Hello');
}, 1000);
这样 setTimeout 接收到的是一个函数,等时间到了才会执行里面的内容。如果要传变量,也很直观:
var name = '张三';
setTimeout(function() {
console.log('你好,' + name);
}, 1000);
这里的 name 就通过闭包被记住了。
利用 bind 方法绑定参数
Function.prototype.bind 可以预设函数的 this 和参数。比如有这样一个回调函数:
function greet(greeting, name) {
console.log(greeting + ',' + name + '!');
}
我们可以预先固定第一个参数:
var sayHi = greet.bind(null, 'Hi');
sayHi('小明'); // 输出:Hi,小明!
在事件监听中也常用到:
button.addEventListener('click', handleClick.bind(null, userId));
这样点击时就能拿到对应的 userId,不用去查 DOM 或者依赖闭包。
箭头函数简化传参
ES6 的箭头函数让包装更简洁:
setTimeout(() => console.log('五秒后见'), 5000);
带参数的情况也干净利落:
users.forEach(user => {
setTimeout(() => notifyUser(user.id), 1000);
});
每个循环里的 user 都被正确捕获,不会出现常见的闭包陷阱。
实际应用场景
假设你在做一个上传文件的功能,每传一个文件,等完成后要提示用户。你可以这样设计:
function uploadFile(file, onSuccess) {
// 模拟上传
setTimeout(() => {
onSuccess(file.name);
}, 2000);
}
uploadFile(myFile, function(fileName) {
alert(fileName + ' 上传成功!');
});
onSuccess 就是一个带参的回调,文件名从上传函数里传回给了调用方。
这种模式在异步编程里非常普遍,理解好回调传参,才能写出清晰可靠的逻辑。