前言
javaScript 语言的执行环境是 单线程。也就是指一次只能完成一个任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
这种模式虽然实现起来比较简单,执行环境相对单纯,但是只要一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往是因为某一段 JavaScript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法运行。
为了解决这个问题,JavaScript 语言将任务的执行模式分为两种:同步和异步。本篇文章主要说一下 异步编程。
1. 异步的概念
异步(Asynchronous , async)是与同步(Synchronous , sync)相对的概念。
在我们学习的传统单线程编程中,程序的运行是同步的(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。
简单的理解就是:同步是按照你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
异步就是从主线程发射一个子线程来完成任务。
2. 什么时候用异步编程
在前端编程中(甚至后端有时也是这样),我们在处理一些简短、快速的操作时,例如计算 1 + 1 的结果,往往在主线程中就可以完成。主线程作为一个线程,不能够同时接受多方面的请求。所以,当一个事件没有结束时,页面将无法处理其他请求。
现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。
为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长已至于被用户察觉的事情,比如读取一个大文件,或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后,就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理其他事情,比如处理来自服务器的信息,我们是无法将其合并到主线程中去的。
为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数来实现异步任务的结果处理。
3. 回调函数
回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态,它自己会善始善终。
具体例子如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>异步操作</title>
</head>
<body>
<p>回调函数等待 3 秒后执行。</p>
<p id="demo"></p>
<script>
function print() {
document.getElementById("demo").innerHTML="RUNOOB!";
}
setTimeout(print, 3000);
</script>
</body>
</html>
这段程序中的 setTiemout 就是一个消耗时间较长(3s)的过程,他的第一个参数是一个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待3s,然后执行回调函数 “print” 。
4. 常见的异步操作
- Ajax请求一般采用异步,也可以设置成同步
- setTimeout、setInterval
- Promise.then
- 回调函数可以理解成异步,但不是严格的异步操作
- 事件监听监听某个事件,当事件发生时,再执行相应操作。