Logo
Section 1 / 4

回调函数

在本节课中,你将了解什么是异步 JavaScript 以及如何使用回调函数。

检查点

回调函数

在本节课中,你将了解什么是异步 JavaScript 以及如何使用回调函数。

JavaScript 是一种单线程编程语言,这意味着同一时间只能发生一件事。虽然单线程简化了代码的编写和推理,但也带来了一些缺点。

想象一下,我们执行一个耗时较长的任务,比如通过网络获取资源。此时,浏览器会被阻塞,直到资源下载完成。这会导致糟糕的用户体验,甚至可能导致用户离开我们的页面。

这时,异步 JavaScript 就派上用场了。使用异步 JavaScript,我们可以在不阻塞主线程的情况下执行长时间运行的任务。

实现异步 JavaScript 有三种方式:

  • 回调函数(Callbacks)
  • Promise
  • Async/Await

在了解这些技术之前,我们先来理解同步代码和异步代码的区别。

同步代码

当我们以同步方式执行代码时,必须等待当前任务完成才能进入下一个任务。在每个操作处理期间,其他任何事情都无法进行,渲染也会暂停。

让我们写一段代码来说明:

1function logFunction() { 2 console.log('in logFunction'); 3} 4 5console.log('Start'); 6logFunction(); 7console.log('End'); 8 9// -> Start 10// -> in logFunction 11// -> End

这段代码按预期执行:

  1. 输出 "Start"。
  2. 执行函数,输出 "in logFunction"。
  3. 输出 "End"。

因此,同步任务彼此之间是相互依赖的,必须按顺序执行。

异步代码

当我们以异步方式执行某项操作时,可以在它完成之前就转去执行其他任务。

如果你想深入了解 JavaScript 异步编程背后的机制,可以查阅事件循环(event loop)相关资料。

让我们看另一个使用 setTimeout() 的例子,它允许我们在指定的毫秒数之后再执行代码:

1console.log('Start'); 2 3setTimeout(() => { 4 console.log('In timeout'); 5}, 1000); // 1秒后执行 6 7console.log('End'); 8 9// -> Start 10// -> End 11// -> In timeout

你是否期望看到 "In timeout" 在 "End" 之前输出?实际上我们并没有阻塞代码执行,而是继续往下走,一秒钟后再回来执行 setTimeout 内部的代码。

再来看一个例子:当我们从服务器获取一张图片时,无法立即返回结果。这意味着下面这样的写法是行不通的:

1let response = fetch('myImage.png'); // fetch 是异步操作 2let blob = response.blob();

这是因为我们不知道图片需要多久才能下载完成。当执行第二行代码时,response 还未准备好,因此会抛出错误。我们必须等到响应返回之后才能使用它。

接下来,我们看看如何用异步代码解决这个问题。

回调函数

这种异步编程的方式是让耗时的操作接收一个额外的参数,称为回调函数。当耗时操作完成后,就会调用这个回调函数,并传入结果。

例如,setTimeout 函数会在指定的毫秒数后调用一个函数。我们可以用这种方式模拟慢速的异步任务,而无需真正调用后端接口:

1setTimeout(() => console.log('一秒钟后执行。'), 1000);

虽然回调函数的概念在理论上很好,但在实际使用中容易导致代码混乱、难以阅读。试想一下层层嵌套的回调:

1getData(function(a) { 2 getMoreData(a, function(b) { 3 getMoreData(b, function(c) { 4 getMoreData(c, function(d) { 5 // ... 6 }); 7 }); 8 }); 9});

这种多层嵌套的回调被称为 回调地狱。每增加一层嵌套,代码的可读性和可维护性都会下降。如今,直接使用回调的情况已经很少见了,但如果你遇到旧项目,仍有可能看到它们的身影。

总结

当我们的代码不会立即完成时,就需要等待其执行完毕才能继续下一步。这正是异步 JavaScript 的作用所在。

在本课中,我们对比了同步与异步 JavaScript 的区别,并学习了最初通过回调函数来解决异步问题的方法。

接下来,我们将学习现代异步 JavaScript 的两种更优方案:Promise 和 async/await。


关于异步 JavaScript 和回调函数的测验

Quiz

多选题

我们编写异步代码的原因有多个,以下都是正确的:

  • 提高程序的响应性:异步代码可以避免阻塞主线程,使程序在等待I/O操作(如网络请求、文件读写)时仍能继续处理其他任务,从而提升用户体验。

  • 提升性能和吞吐量:通过并发执行多个任务,异步编程可以更高效地利用系统资源,尤其是在处理大量并发请求时,能够显著提高系统的整体性能。

  • 优化资源使用:异步操作减少了对线程或进程的依赖,降低了内存占用和上下文切换开销,使得系统可以支持更多并发连接。

  • 支持高并发场景:在Web服务器、实时通信应用等需要同时处理成千上万连接的场景中,异步编程是实现高性能的关键。

1 / 3

游戏化

Promise