函数可以像“变量”一样传递
在 JavaScript 里:函数也是一种数据。所以函数可以:
- 赋值给变量
- 作为参数传递
- 作为返回值
function hello() {
console.log("hello");
}
let fn = hello;
fn(); // 输出:hello
/*
hello → 函数
fn → 指向这个函数
*/
什么是 callback(回调函数)
callback 就是 作为参数传给另一个函数的函数,并在之后被调用。
- 函数 A 把函数 B 当作参数
- 函数 A 在某个时刻执行函数 B
function greet(name, callback) { // 名字不唯一,如果callback改成ABC
console.log("Hello " + name);
callback(); // 这里也要改成ABC
}
function sayBye() {
console.log("Bye!");
}
greet("Tom", sayBye);
执行过程:
调用 greet("Tom", sayBye)
greet 内部:
1 打印 Hello Tom
2 执行 callback()
callback 指向 sayBye
于是执行 sayBye()
callback 与异步回调
在 JavaScript 中,回调函数(callback)实际上是用来 处理异步操作 的。当你需要等待一些 异步事件(如网络请求、定时器、文件读取等)时,把需要等待的代码放进回调函数里,就能保证这些操作不会阻塞主线程的执行。
// 模拟网络请求
function fetchData(callback) {
console.log("请求开始");
setTimeout(() => {
console.log("数据返回");
callback("数据");
}, 2000); // 假设 2 秒后返回数据
}
console.log("请求之前");
fetchData((data) => {
// 这段代码是需要等网络请求结束后再执行的代码
// 如果主线程不愿意等待,就放在异步回调函数里面
console.log("接收到数据:", data);
});
console.log("请求之后");
/*
输出:
请求之前
请求开始
请求之后
数据返回
接收到数据: 数据
*/
回调作为对象属性
事件监听:UI 交互
在前端开发中,回调函数作为对象的属性最常见的用途就是 事件监听,例如监听按钮点击、表单提交、输入框变化等。
const button = {
onClick: null, // 事件回调初始化为空
// 按钮点击事件处理
click() {
console.log("按钮被点击了!");
if (this.onClick) {
this.onClick(); // 执行回调函数
}
}
};
// 注册点击事件回调
button.onClick = () => {
console.log("按钮点击事件已处理!");
};
// 触发点击事件
button.click();
这里,onClick 是按钮对象的 回调属性,我们把回调函数赋值给 onClick,当 click 方法触发时执行该回调。
典型用途:
- 按钮点击
- 用户输入(如表单、输入框变化)
- 窗口大小变化
- 页面滚动等
异步操作回调(例如,网络请求)
在异步编程中,回调作为对象属性也非常常见,通常用来处理 异步操作的结果。
const dataLoader = {
onDataLoaded: null, // 数据加载完成的回调
// 模拟加载数据
loadData() {
setTimeout(() => {
console.log("数据加载完成!");
// 执行回调
if (this.onDataLoaded) {
this.onDataLoaded("加载的数据");
}
}, 2000);
}
};
// 注册数据加载完成的回调
dataLoader.onDataLoaded = (data) => {
console.log("接收到的数据:", data);
};
// 调用 loadData,模拟数据加载
dataLoader.loadData();
在这个例子中,onDataLoaded 是回调函数,它作为 dataLoader 对象的属性。loadData 方法模拟一个异步操作(通过 setTimeout),并在数据加载完成后调用回调。
典型用途:
- 网络请求完成后的回调(例如 AJAX 请求)
- 定时器到期后的回调(例如
setTimeout、setInterval) - 文件读取完成后的回调
自定义事件系统
回调作为对象的属性也可以用来实现 自定义事件系统。在这种情况下,回调函数用来处理 自定义的事件。
class EventEmitter {
constructor() {
this.events = {}; // 存储事件名与回调函数
}
// 订阅事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
// 触发事件
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
const emitter = new EventEmitter();
// 订阅事件
emitter.on('dataReceived', (data) => {
console.log("数据接收成功:", data);
});
// 触发事件
emitter.emit('dataReceived', { id: 1, name: 'Tom' });
这里,我们创建了一个 EventEmitter 类,允许注册回调函数(通过 on 方法)并触发事件(通过 emit 方法)。回调函数在 emit 时触发,处理特定事件的数据。
典型用途:
- 自定义事件系统(例如,Node.js 的
EventEmitter类) - 发布/订阅模式
- 组件间通信
在方法里面使用回调函数
异步操作处理
在处理异步操作时(比如等待网络请求、定时器等),我们会把回调函数作为参数传入,并在异步操作完成后执行回调。
function fetchData(callback) {
setTimeout(() => {
const data = { user: "Tom", age: 25 };
// 异步任务完成后执行回调
callback(data);
}, 2000);
}
fetchData((data) => {
console.log("接收到数据:", data);
});
在这个例子中,fetchData 方法通过 setTimeout 模拟了一个异步操作,等操作完成后,通过回调函数传递数据并执行回调逻辑。
这种方式的常见用途:
- 网络请求(如
fetch、axios等) - 处理异步任务(如文件读取、图片加载等)
- 定时器处理(如
setTimeout、setInterval)
数组处理
JavaScript 数组方法(如 forEach、map、filter)都广泛使用回调函数来 定制每个元素的处理。这是在方法内部使用回调的一个典型例子。
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num) => {
console.log(num * 2); // 每个数字乘以 2
});
在 forEach 中,传入的回调函数会被 依次应用到数组的每个元素。
这种方式的常见用途:
- 数组遍历
- 数组过滤(
filter) - 数组映射(
map)
自定义逻辑操作
方法内的回调函数可以让你 动态定制方法的行为,这非常常见,尤其在你希望某个方法的执行逻辑有很强的 可扩展性 和 灵活性 时。
function processData(data, callback) {
const result = data.map((item) => item * 2);
// 执行回调,传递处理后的数据
callback(result);
}
processData([1, 2, 3], (result) => {
console.log("处理后的数据:", result);
});
在这个例子中,processData 接收一个回调函数 callback,它在处理完数据后调用回调并传递处理结果。你可以通过传递不同的回调来执行不同的操作。
这种方式的常见用途:
- 数据处理(如格式转换、聚合操作等)
- 任务调度(如对多个任务的顺序或并发控制)