JavaScript异步编程---同步模式、异步模式、回调函数

概述

众所周知,当前主流的JavaScript环境都是以单线程模式去执行代码的。
其原因和当时设计该语言的初衷有关系,最早这门语言就是运行在浏览器上的脚本语言,目的是为了实现页面上的动态交互,其核心就是dom操作,该点决定了他必须要使用单线程模式,否则就会出现很复杂的线程同步问题。
该种模式的有点很明显就是安全,但是缺点也很明显,万一遇到某个特别耗时的操作,后面的任务都需要排队等待前面执行完毕。这样就会导致出现假死的情况。
为了解决上述的问题,将任务执行的模式分成了同步模式和异步模式。
下面将要涉及到的内容有:

  1. 同步模式与异步模式
  2. 事件循环与消息队列
  3. 异步编程的几种方式
  4. Promise异步方案、宏任务/微任务队列
  5. Generator异步方案、Async/Await语法糖

同步模式

顾名思义就是代码按顺序依次执行,后一个任务只有在钱一个任务执行完后才会执行。
单线程的情况下,大多数任务都会以同步模式执行,注意这个同步是指排队执行,不是同时执行。
下面以一段代码分析同步模式的执行过程。

首先JavaScript会在内部调用栈压入一个匿名的调用,可以理解为将所有代码都放入一个匿名函数中准备执行。

"JavaScript异步编程---同步模式、异步模式、回调函数
然后先执行代码中的console.log(“start”)方法,执行完成后弹出。
函数的声明不会产生任何的调用,所以执行会继续往下走,随后执行test2(),压入调用栈,在执行test2()中,调用了test1(),也同样压入调用栈,在执行完毕后同样依次弹出调用栈。
调用栈中的内容清空时,也意味着这次的执行就结束了。

异步模式

不同于同步模式,异步模式的API不会等待这个任务的结束才开始下一个任务,对于耗时操作,异步模式会在开启之后立即往后执行下一个任务,后续逻辑一般会通过回调函数的方式去定义,在内部,好事任务完成之后就会自动执行传入的回调函数。
相比于同步模式,代码执行的顺序更加的混乱和难懂。
JavaScript异步编程---同步模式、异步模式、回调函数
首先也是加载整体的代码,在调用栈中压入一个匿名的全局调用,回一次执行每行代码,对于console.log这样的同步api还是和同步模式一样的,先压栈,打印,再弹栈,然后就遇到了一个异步调用方法,同样也是首先将该调用压入调用栈,但是由于是异步调用,所以我们需要关注内部环境做了什么事情,在这个案例中,内部就是为这个函数开启了一个倒计时器,然后这个倒计时器会被单独放在一边,在开启倒计时后,对于settimeout的调用就执行完了,会弹栈,代码继续往下执行,再往下又遇到了一个settimeout,同理也是压栈,开启计时器,弹栈,最后执行console的打印。
这时,调用栈中已经清空了,这时候event loop开始起作用,负责监听调用栈和消息队列,一旦调用栈清空了,event loop就会从消息队列取出第一个回调函数,然后压入调用栈。由于两个倒计时的时间不同,明显的time2的计时器结束的更快,所以先结束,结束完了time2函数就会被放入消息队列的第一位,time1结束后就会放入第二位。一旦消息队列发生了变化,event loop就会监听到,然后把首个回调函数压入调用栈,开始新一轮的执行。
消息队列可以理解成一个待办的工作表。
总的来说异步调用的时序图如下图所示:
JavaScript异步编程---同步模式、异步模式、回调函数

回调函数

实现异步编程方法的根本就是回调函数,时异步编程的根基。
由调用者定义,交给执行者执行的函数就叫回调函数。
简单的实例:
JavaScript异步编程---同步模式、异步模式、回调函数