什么是回调函数
回调函数的讲解
回调函数是作为参数传递给另外一个函数,在主函数执行后或在该函数确定的点执行。
回调是JavaScript异步编程模型的支柱,也是Javascript在Web开发中如此强大的原因。
特点:可以支持非阻塞代码的执行,允许应用程序在执行等待API调用、用户交互或定时器操作完成时继续执行下去。
示例代码:
js
// 基础的回调案例
function greet(name, callback) {
console.log('hello ' + name)
callback()
}
function callbackFunction() {
console.log('Callback function executed!');
}
greet('Tom', callbackFunction);
// 输出
// hello Tom
// Callback function executed!
示例中greet
是主函数,callbackFunction
作为参数传入greet
中,主函数执行回调函数同时跟着执行。
特别提示:回调函数是JavaScript中异步编程的基础,可以处理耗时操作而不阻塞主线程。这对于创建响应式Web应用程序至关重要,这些应用程序在执行获取数据或处理大型数据集等任务不会卡顿。
语法和用法:
js
// 回调函数
function doSomething(callback) {
callback()
}
// 普通函数作为回调函数
doSomething(function() {
console.log('callback executed')
})
// 这是一个箭头函数作为回调函数
doSomething(() => {
console.log('callback executed')
})
关于回调的要点:
- 它们是可以命名函数或匿名函数
- 它们可以接受参数
- 它们可以是箭头函数
- 它们通常用于异步函数
- 它们可以嵌套使用(可能导致回调地狱)
- 它们支持JavaScript函数式编程模式
- 它们允许控制反转,接受回调的函数决定何时以及如何执行
下面结合几个示例讲解说明
示例1 - 浏览器中的事件处理
js
const button = document.getElementById('button');
button.addEventListener('click', function(event) {
console.log('按钮被点击了')
console.log('坐标:', event.clientX, event.clientY)
console.log('目标元素:', event.tatget)
event.preventDefault();
event.stopPropagation();
})
button.addEventListener('click', () => {
console.log('按钮被点击了')
console.log('事件类型', event.type)
})
方法addEventListener
就是作为主函数,回调函数则是第二个参数,同时回调函数接受一个参数event
,描述了当前点击的对象属性以及所携带的方法。
这种模式可以实现:
- 响应式用户界面,可对用户交互做出反应
- 解耦时间处理逻辑,提高代码的可维护性
- 同一事件的多个处理程序
- 动态时间处理
- 事件委托,用户有效处理多个元素上的事件
说明:如果没有回调,交互式web程序几乎不可能实现,因为无法以非阻塞的方式响应用户操作。
示例2 - 带有回调的数组方法
js
const numbers = [1, 2, 3, 4, 5]
// forEach
numbers.forEach(function(number, index, array) {
// number: 当前的元素
// index: 当前元素的索引值
// array: 数组本身
console.log(number, index, array)
})
// map
// 对数组中每个元素乘以2
const doubled = numbers.map((number, index) => {
return number * 2
})
// ...
带有回调的数组方法是用于数据转换和操作的强大工具,这些方法实现了函数式编程模式,使代码更具声明性,可读性。
- forEach:对每个元素执行回调;无法停止(与for循环不同);返回未定义(undefined);非常适日志记录或DOM更新等副作用;比传统的for循环更易读的替代方案。
- map:使用转换后的元素创建一个新数组;必须在回调中返回一个值;保持相同的数组长度;非常适合进行数据转换而不改变原数组;常用于框架渲染。
- 其他关于带有回调的数组方法:filter、find、findIndex、some、sort、flatMap等。
示例3 - 带有回调的异步操作
js
function fetchData(url, successCallback, errorCallback, loadingCallback) {
loadingCallback(true);
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('')
}
return response.json();
})
.then(data => {
loadingCallback(false)
successCallback(data)
})
.catch(error => {
loadingCallback(false)
errorCallback(error)
})
}
异步操作是回调真正发挥作用的地方,并展示了在JavaScript中重要作用。
好处:
- 非阻塞操作可以让应用程序保持响应
- 为应用程序提供适当的错误处理
- 加载状态的管理可以获得更好的用户体验
- 针对不同的场景灵活响应处理
- 数据获取和Ui更新分离
提示:虽然JavaScript提供了Promise和async/await作为处理异步函数更优雅的解决方法,但是理解回调至关重要,因为他们构成了这些新模式的基础,并且许多框架和库都有使用。
示例4 - 带有回调的自定义高阶函数
js
// 创建一个自定义高阶函数
function retryOperation(operation, maxAttempts, delay, callback) {
let attempts = 0;
function attempt() {
attempts++;
try {
const relult = operation();
callback(null, result)
} catch (error) {
console.error()
if (attempts < maxAttempts) {
setTimeout(attempt, delay)
} else {
callback()
}
}
}
attempt()
}
提示: 处理多个异步操作时,请考虑使用Promise.all() 或async/await 而不是嵌套回调,以保持代码的可读性,并避免'回调地狱'和'末日金字塔',这些会使代码难于维护。
常见的面试题
- 什么是回调函数,为什么使用回调函数
- 什么是回调地狱已经如何避免它
- 回调和异步编程有何关系
备注:本人前端菜鸟一枚,欢迎指出其中的不足。