一、什么是回调函数
回调函数(Callback Function)是指将一个函数的定义作为参数传递给另一个函数,并在特定条件(比如用户鼠标点击、事件触发等)满足时被调用的函数。
回调函数在被作为参数传递以后,并不会立即执行,它需要等待特定的条件才能被触发并执行,这种模式可以让程序在运行期间单独去执行已经被开发者设定好了的特定任务的代码块。
在Node.Js编程中,回调函数在处理异步任务(比如读取文件、发起新的请求、在某些事件后执行代码)方面起着至关重要的作用。
回调函数为Node.Js编程提供了一个闭包的实现,它能访问到函数外面定义的其他外部变量。
回调函数的目的是实现延迟执行或者等待当前任务完成后再执行下一个任务,因此,回调函数是实现异步编程的重要核心机制之一。
Node.Js回调函数的编码范式:
function func(x, callback_func) {
callback_func(x)
}
二、回调函数的特点
支持异步执行:处理API调用、计时器和事件等异步任务的同时,不会阻塞当前任务的执行。
代码可重用性高:通过为不同的场景配置不同的回调函数来编写模块化的可复用的代码。
事件驱动编程:执行哪些回调函数都是由发生了哪些特定事件来决定的,常见的事件有鼠标点击、按键输入等。
支持错误处理:将错误信息传递给回调函数,以便在异步任务执行期间发生错误时可以更好地进行处理。
支持非阻塞运行:可以在异步执行复杂任务的同时,让主线程保持空闲。
三、回调函数的基础语法
1.JavaScript/Node.Js中间隔执行回调函数的语法
JavaScript中有两种方法可以按特定时间间隔来执行回调函数的代码,分别是
-
setInterval()
-
setTimeout()
(1).setInterval的用法:
const intervalID = setInterval(callback, delay);
const intervalID = setInterval(callback, delay, arg1, arg2, ...);
参数说明:
callback:需要定时执行的回调函数。
delay:间隔时间,以毫秒为单位,默认值为0 ms。
arg1, arg2:需要传递给回调函数的参数。
返回值intervalID:定时器ID,可用于执行clearInterval来停止。
代码实战:
// 每秒输出一次
const interval_1 = setInterval(() => {
console.log("Interval_1 triggered");
}, 1000);
// 传递参数
function logMessage(message) {
console.log(message);
}
const interval_2 = setInterval(logMessage, 2000, "Interval_2 triggered");
// 清除定时器
setTimeout(() => {
clearInterval(interval_1);
console.log("Interval_1 stopped");
}, 3000);
setTimeout(() => {
clearInterval(interval_2);
console.log("Interval_2 stopped");
}, 5000);
运行结果:
Interval_1 triggered
Interval_2 triggered
Interval_1 triggered
Interval_1 stopped
Interval_2 triggered
Interval_2 stopped
(2).setTimeout的用法:
const timeoutID = setTimeout(callback, delay);
const timeoutID = setTimeout(callback, delay, arg1, arg2, ...);
参数说明:
callback:需要定时执行的回调函数。
delay:延迟时间,以毫秒为单位,默认值为0 ms。
arg1, arg2:需要传递给回调函数的参数。
返回值timeoutID:定时器ID,可用于执行clearTimeout来停止。
代码实战:
const timer1 = setTimeout((name, age) => {
console.log(`${name}, age is ${age}`);
}, 2000, "Frank", 29);
function showMessage(msg) {
timer2 = setTimeout(() => {
console.log(msg);
}, 500);
}
showMessage("show age");
运行结果:
show age
Frank, age is 29
注意:setInterval()和setTimeout()共享同一个定时器ID池,这导致clearInterval()和clearTimeout()在编码上互换使用也不会出错,但是为了让代码更加清晰可读,应该加以区分,以避免维护代码时产生混淆。
2.Node.Js同步回调
同步回调,又被称为阻塞式回调,即在函数A中直接调用并执行函数B,和普通的函数调用没有明显区别。
同步回调的优点是代码实现简单,缺点是它会在当前的主线程中立即执行,这会阻塞后续代码的执行。
同步回调常用于数组的遍历等场景。
代码样例:
const numbers = [1, 2, 3];
numbers.forEach(num => {
console.log(`processing ${num}`);
});
运行结果:
processing 1
processing 2
processing 3
3.Node.Js异步回调
异步回调,又被称为非阻塞式回调,即回调函数最常用的实现方式,它不会阻塞后续代码的执行。
异步回调常用于I/O操作、定时任务、事件处理、网络请求等场景。
同步回调是立即执行,异步回调是将任务放进队列,等事件触发了再执行。
代码样例:
//普通函数
function greet(name, callback) {
console.log('Hello' + ' ' + name);
callback();
}
//回调函数
function callback_func() {
console.log('Print from callback function');
}
//将回调函数作为参数传递
greet('Jim', callback_func);
运行结果:
Hello Jim
Print from callback function
4.回调地狱
回调地狱(Callback Hell),是指由多层嵌套的回调函数导致的代码可读性差、维护困难的现象。它通常出现在处理异步操作(如文件读取、网络请求、定时任务等)时,需要按顺序执行多个异步任务的场景。
回调地狱的典型代码结构:
doTask1(() => {
doTask2(() => {
doTask3(() => {
// 难以维护的嵌套...
});
});
});
模拟回调地狱的代码:
function asyncTask1(callback) {
setTimeout(() => {
console.log("mission1 finished.");
callback();
}, 100);
}
function asyncTask2(callback) {
setTimeout(() => {
console.log("mission2 finished.");
callback();
}, 100);
}
function asyncTask3(callback) {
setTimeout(() => {
console.log("mission3 finished.");
callback();
}, 100);
}
function asyncTask4(callback) {
callback();
}
// 回调地狱的典型结构
asyncTask1(() => {
asyncTask2(() => {
asyncTask3(() => {
asyncTask4(() => {
console.log("All missions finished.");
});
});
});
});
运行结果:
mission1 finished.
mission2 finished.
mission3 finished.
All missions finished.
避免产生回调地狱的方法:
1、禁止在循环中直接使用异步回调。
2、优先使用ES2017以上标准的异步处理方案,比如Promise链式调用,async/await等。
以上代码可以优化为:
function asyncTask1() {
setTimeout(() => {
console.log("mission1 finished.");
}, 100);
}
function asyncTask2() {
setTimeout(() => {
console.log("mission2 finished.");
}, 100);
}
function asyncTask3() {
setTimeout(() => {
console.log("mission3 finished.");
}, 100);
}
async function runTasks() {
try {
await asyncTask1();
await asyncTask2();
await asyncTask3();
} catch (error) {
console.error("Error: ", error);
}
}
runTasks();
运行结果:
mission1 finished.
mission2 finished.
mission3 finished.
四、回调函数的应用场景
场景1,处理异步请求,当需要处理文件读写、网络请求(如fetch或XMLHttpRequest)或数据库操作等异步任务时,可以使用回调函数。
代码样例:
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('文件内容:', data);
});
场景2,当发生JavaScript DOM事件(如点击、键盘输入)或Node.js事件时,回调函数用于响应特定事件。
代码样例:
document.querySelector('button').addEventListener('click', () => {
console.log('按钮被点击!');
});
场景3,处理定时或延迟任务时,比如使用setTimeout/setInterval函数,使用回调函数实现延迟执行。
代码样例:
setTimeout(() => {
console.log('2秒后执行');
}, 2000);
场景4,设计用户自定义行为时,采用回调函数实现用户期望的处理逻辑。
代码样例:
app.get('/api/data', (req, res) => {
res.send('自定义响应');
});
场景5,处理异步的API请求,等待API的执行结果
代码样例:
function fetch(callback) {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(data => callback(data))
.catch(error => console.error("Error:", error));
}
function handle(data) {
console.log("Fetched Data:", data);
}
fetch(handle);
五、编码实战
Demo1:
1.通过CSS创建可视化点击区域。
2.使用addEventListener监听用户的点击事件。
3.组合使用了命名回调函数和匿名回调函数两种形式。
4.跟踪点击的位置信息clientX/clientY。
代码实现:
<!DOCTYPE html>
<html>
<head>
<style>
#clickArea {
width: 200px;
height: 200px;
background: #5a9b71;
border: 2px solid #093b27;
text-align: center;
line-height: 200px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="clickArea">点击这里</div>
<script>
// 获取DOM元素
const clickArea = document.getElementById('clickArea');
// 定义回调函数
const handleClick = (event) => {
clickArea.textContent = `点击坐标:(${event.clientX}, ${event.clientY})`;
clickArea.style.backgroundColor = '#e0e0ff';
};
// 绑定点击事件监听器(使用匿名函数作为回调)
clickArea.addEventListener('click', (e) => {
// 调用命名回调函数
handleClick(e);
// 附加的匿名回调逻辑
setTimeout(() => {
alert('点击已处理!');
}, 100);
});
</script>
</body>
</html>
运行结果:
参考阅读:
https://www.w3schools.com/js/js_callback.asp
https://www.geeksforgeeks.org/javascript-callbacks/
https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/callback.html
https://www.geeksforgeeks.org/how-to-create-a-custom-callback-in-javascript/