三分钟看懂回调函数

回调函数的概念

很多文章当中,都会用一个非常专业的概念来告诉大家什么是回调函数。他们给出的概念基本都是这样的

回调函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。回调函数通常用于事件处理、异步编程和处理各种操作系统和框架的API。

但是假如你第一次接触这个概念,是不会搞明白他的定义到底阐述了他的什么功能的。至少对于笔者来说,第一次接触的时候很难搞懂。

但是这个定义给了我们四个回调函数的关键点

  • 主程序:发起调用并传递回调函数的程序 / 线程
  • 被调用的程序: 接收回调函数,并在特定事件发生时触发它的程序 / 线程
  • 回调函数: 封装了后续处理逻辑的代码块
  • 处理程序: 执行回调函数中具体逻辑的代码

这四个关键点可能总结的不够精确和形象。概念总是抽象的。

我们用两个生活当中的例子来解释什么是回调函数,让你对这个机制有一个初步的印象

餐厅点餐

  1. 你(主程序) :走进餐厅,点了一份披萨。
  2. 服务员(被调用的函数) :接过订单去厨房处理。
  3. 回调函数(你的承诺) :你告诉服务员:"披萨做好后,请叫我一声,我来取。"
  4. 处理程序 :你听到服务员叫你去拿披萨,你去取餐点拿披萨

从这个流程当中,我们可以看到上面的四个回调函数的关键点,在日常生活中还是很好体现的

我们换一个例子再看看这个回调函数是怎么工作的

网购收货

  1. 你(主程序) :去网站下单购买一本书。
  2. 快递公司(被调用的函数) :接过订单开始配送。
  3. 回调函数(你的承诺) :你告诉快递员:"书到驿站,请叫我一声,我来取。"
  4. 处理程序 :你听到快递员的电话,你去驿站拿快递

回调函数的核心思想

其实所谓的核心思想,就是使用回调函数的时候,他所体现出的核心特性:

  • 主程序不需要等待: 你不必一直盯着厨房,点完餐可以刷手机、看报纸。
  • 事件触发回调 : 当披萨做好了,也就是约定的触发回调函数的事件 发生了,那服务员就会主动调用你的回调 ,喊你来取餐。这就是为什么回调函数的英文名叫CallBack
  • 解耦逻辑: 服务员不需要知道你拿到披萨之后做什么,是直接吃,还是拍照发朋友圈,还是打包带走。这些他都不知道。他只负责调用这个回调函数。你要做什么事情,都封装在回调函数里面。

回调函数的使用场景

回调函数的核心特性(无需等待、事件触发、解耦逻辑)使其在很多场景中成为 "最优解"。

异步操作--主程序需要等待某个结果,但是不想阻塞主程序的运行

核心需求:程序执行到某一步时,需要依赖一个耗时操作(如网络请求、文件读写、数据计算),但又不想让整个程序卡住等待

你在奶茶店点了一杯奶茶(耗时操作),告诉店员 "做好了放柜台上喊我一声"(回调),然后你去旁边便利店买东西(主程序继续执行),听到喊你的时候再回来取(触发回调)。

假如没有这个回调的过程,那你就要一直在奶茶店的前台等到店员把奶茶做好,才能继续去旁边的便利店买东西。你生活的效率就下降了

在前端的开发当中,回调函数被大量的使用。 前端发送网络请求获取数据时,不用阻塞页面交互,而是定义 "数据返回后渲染页面" 的回调函数,请求完成后自动执行。

假如你在刷抖音的时候,点击 "查看更多评论" 按钮:

  • 如果采用同步请求
    点击按钮 → 浏览器发送请求 → 等待服务器返回数据(期间页面卡住,无法滑动、点击) → 数据返回后渲染评论。
    用户体验:点击后页面 "卡死",必须等待数据加载完成才能继续操作。
  • 如果采用异步请求 + 回调
    点击按钮 → 浏览器发送请求(同时页面可以继续响应用户操作) → 数据返回后自动触发回调函数 渲染评论。
    用户体验:点击后可以继续刷视频、点赞,评论会 "悄悄" 加载完成并显示。

现在的前端技术往往采用fetch APIaxios,结合 Promiseasync/await,让回调更优雅:

javascript 复制代码
document.getElementById('loadComments').addEventListener('click', async function() {
  try {
    // 显示加载中
    document.getElementById('commentList').innerHTML = '<div>加载中...</div>';

    // 发送异步请求(本质还是回调,但语法更简洁)
    const response = await fetch('/api/comments');
    const comments = await response.json();

    // 数据返回后自动执行(相当于回调)
    renderComments(comments);
  } catch (error) {
    // 错误处理(另一种回调)
    document.getElementById('commentList').innerHTML = '<div>加载失败</div>';
  }
});

回调函数让前端可以 "先发起请求,然后忙其他事,数据回来后再处理"


事件驱动--需要监听某个动作,这个动作发生之后程序会自动响应的场景

核心需求:程序需要时刻 "监听" 用户操作或系统事件(如点击、输入、定时),事件发生时自动执行对应的处理逻辑。

比如你晚上睡觉之前,设置了手机闹钟。这其实就是一个监听事件,监听的是时间的流逝。定了早上起点的闹钟,铃声是《阳光彩虹小白马》,那这个闹钟在早上七点的时候,监听的时间时间就发生了。他就开始执行对应的处理逻辑--播放《阳光彩虹小白马》

按钮点击事件( onclick )、鼠标移动事件( onmousemove ),本质都是将回调函数绑定到事件上,事件触发时自动调用。

这个场景甚至比上面的使用还要广泛,前端是离不开这个功能的

但是这里面又涉及到专业的名词--事件绑定

  • 事件 :浏览器自动捕获的用户操作或系统状态变化(如点击、滚动、加载完成)。
  • 事件监听器 :一段代码,当特定事件发生时执行。
  • 回调函数:事件发生时被调用的函数,本质是 "等待被触发的代码"。
java 复制代码
<!DOCTYPE html>
<html>
<body>
  <button id="myButton">点击我变色</button>

  <script>
    // 获取按钮元素
    const button = document.getElementById('myButton');

    // 定义回调函数:改变按钮颜色
    function changeColor() {
      // 随机生成颜色
      const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16);
      button.style.backgroundColor = randomColor;
      button.textContent = `当前颜色: ${randomColor}`;
    }

    // 将回调函数绑定到点击事件
    button.onclick = changeColor;

    // 也可以用现代写法(推荐)
    // button.addEventListener('click', changeColor);
  </script>
</body>
</html>

事件回调让页面 "随时待命",用户操作时自动响应

通用逻辑复用--需要固定流程不变,但是细节步骤可变的场景

有一个通用的流程(如 "处理订单""遍历数据"),但流程中的某个步骤需要根据具体需求定制,不想重复编写整个流程。 假如说对于结果的处理流程需要具体定制,那我们就使用回调函数来进行不同的处理

外卖平台的 "配送流程" 是固定的(接单→取餐→配送→通知),但 "通知用户" 的方式可以定制:有人要电话通知(回调 A),有人要短信通知(回调 B),平台只需在配送完成后调用对应的回调即可。

这部分更多是在于回调的思想而不是在于回调的技术。

比如说,map是数组的内置方法,作用是遍历数组对每个元素做处理,返回新数组。其中,遍历和生成新数组是固定流程,但是如何处理每个数组 ,也就是不同的转换规则 (细节步骤)是由map方法来定义的

Java 中通过 Stream API 实现类似 JavaScript map 的功能,核心是 stream().map() 方法,其中 map 接收一个 Function 接口作为回调。

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        // 原始数组:[1, 2, 3, 4]
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4);

        // 需求1:将每个数字乘以2 → [2, 4, 6, 8]
        List<Integer> doubled = numbers.stream()
        .map(num -> num * 2) // 回调函数:Lambda 表达式实现 Function<Integer, Integer>
        .collect(Collectors.toList());

        // 需求2:将每个数字转为字符串 → ["1", "2", "3", "4"]
        List<String> stringified = numbers.stream()
        .map(Object::toString) // 方法引用简化回调
        .collect(Collectors.toList());

        // 需求3:计算每个数字的平方 → [1, 4, 9, 16]
        List<Integer> squared = numbers.stream()
        .map(num -> num * num)
        .collect(Collectors.toList());

        System.out.println("doubled: " + doubled);     // 输出: [2, 4, 6, 8]
        System.out.println("stringified: " + stringified); // 输出: [1, 2, 3, 4]
        System.out.println("squared: " + squared);     // 输出: [1, 4, 9, 16]
    }
}
  • 固定流程stream().map().collect() 定义了 "遍历→处理→收集" 的流程。
  • 可变细节 :通过 map 传入的 Function 接口(如 num -> num * 2)定义每个元素的处理逻辑

分层协作--需要 "上层告诉下层怎么做,下层专注自己的事" 的场景

在业务流程当中,往往是分为多层的。比如说业务层和工具层。工具层就负责基础操作,比如数据库的读写。业务层负业务流程上的具体逻辑。工具层不需要知道业务层具体的业务逻辑是什么,只需要在自己的工作完成之后调用业务层给出的回调

这样的代码思路在日常编程的过程当中非常常见,因此在这里我就不写出具体的实例了。

一句话总结回调函数

当你需要 "让程序在特定时机自动做某件事,同时不耽误其他工作" 时,就适合用回调函数。它就像生活中 "提前说好的约定",到了时间 / 条件满足,就会自动兑现。

相关推荐
tan180°1 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
优创学社22 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术2 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理2 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
ai小鬼头3 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
简佐义的博客4 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
Code blocks4 小时前
使用Jenkins完成springboot项目快速更新
java·运维·spring boot·后端·jenkins
追逐时光者4 小时前
一款开源免费、通用的 WPF 主题控件包
后端·.net