当前位置:首页 > 技术 > 正文内容

深入理解 JavaScript 回调函数及其必要性

访客 技术 6

JavaScript 将函数视为一等公民。这意味着函数可以被赋值给变量、作为参数传递、从其他函数返回。这一特性是回调函数的基础,理解它至关重要。

函数即值

先明确一点:函数本身也是值。

function greet(name) {
  console.log("Hello, " + name);
}

const sayHello = greet;
sayHello("Samad"); // Hello, Samad

这里并没有调用 greet,而是将其赋值给变量。传递的是函数本身,而非其执行结果。这是所有后续概念的基础。

回调函数是什么

回调函数就是一个被传入另一个函数的函数,以便在合适的时机被调用。

function doSomething(callback) {
  console.log("Doing something...");
  callback();
}

function done() {
  console.log("All done!");
}

doSomething(done);

输出:

Doing something...
All done!

这里没有直接调用 done(),而是将其传递给 doSomething,并告知:“当你准备好时,调用这个函数。”

异步编程中的回调

JavaScript 是单线程的,同一时间只能做一件事。但某些操作(如网络请求、文件读取、定时器)需要时间。如果 JavaScript 一直等待,页面会冻结。

因此,JavaScript 会这样说:“启动这个任务,完成后调用这个函数。”这个函数就是回调。

console.log("Before");

setTimeout(function () {
  console.log("Inside timeout");
}, 2000);

console.log("After");

输出:

Before
After
Inside timeout

注意,“After”在“Inside timeout”之前打印,尽管它在代码中位于 setTimeout 之后。JavaScript 没有等待,而是继续执行,并在定时器到期时调用回调。

这就是回调存在的原因:它们让你能够说“做这件事,完成后运行这段代码。”

将函数作为参数传递

通过一个更清晰的例子来具体说明:

function processData(data, callback) {
  const result = data.toUpperCase();
  callback(result);
}

processData("hello world", function (output) {
  console.log("Processed:", output);
});

输出:

Processed: HELLO WORLD

执行工作的函数 processData 不关心如何处理结果。那是调用者的事情。你提供一个回调,它传入结果,你决定如何处理。这种分离使函数保持干净和可复用。

回调的常见场景

回调在 JavaScript 中随处可见。以下三个场景你一定会经常遇到。

事件监听

document.getElementById("btn").addEventListener("click", function () {
  console.log("Button clicked!");
});

第二个参数是回调。浏览器保存它,只在点击发生时调用。

数组方法

const numbers = [1, 2, 3, 4];

const doubled = numbers.map(function (num) {
  return num * 2;
});

console.log(doubled);

输出:

[2, 4, 6, 8]

map 对每个元素执行回调,并用返回值构建新数组。

定时器

setTimeout(function () {
  console.log("Ran after 1 second");
}, 1000);

经典示例。回调在延迟后执行,而非立即。

回调嵌套的问题

回调在链式异步操作时会变得棘手。如果一个操作完成后需要执行另一个,再执行下一个:

setTimeout(function () {
  console.log("Step 1 done");

  setTimeout(function () {
    console.log("Step 2 done");

    setTimeout(function () {
      console.log("Step 3 done");
    }, 1000);

  }, 1000);

}, 1000);

输出:

Step 1 done
Step 2 done
Step 3 done

代码可以工作,但注意其结构。每一步都向右缩进,形成了“回调地狱”或“末日金字塔”。嵌套越深,代码越难读、调试和维护。

这不是性能问题,而是可读性和可维护性问题。这正是 Promise 和 async/await 所要解决的,但那是另一个话题了。

回调之所以存在,是因为 JavaScript 是单线程且天生异步的。它们让你能够交出控制权,并说“完成后运行这个”。它们用于事件处理、数组方法、定时器以及几乎所有异步操作。

概念很简单,但过度嵌套会带来麻烦。那时你就会寻求更好的工具,但只有理解了回调为何存在及其不足,你才能真正欣赏那些工具。

返回列表

上一篇:从零开始搭建首个 Node.js 项目实战指南

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。