众所周知(不知道的话去查),js是以单线程的方式执行的,在执行的过程中,某一时刻上只能执行一个任务,也就是说,我们写好了代码后执行的时候,程序是根据代码从上到下依次排队执行,只有上一个任务执行完以后才会继续执行下一个任务。但这带来了一个问题,如果执行的过程中,有一个任务需要花费很长事件去请求某一数据,按照单线程同步执行的规则,下面的任务就要等待这一任务执行完毕以后才会继续上任务栈执行,这肯定是不行的。那怎么解决呢?
异步任务与事件循环(Event Loop)
上面我们提到了js的单线程同步执行机制,以及带来的问题,那这个问题的解决就涉及到了异步这个概念,从字面意思上就能看出,异步便是不同于同步范畴的一个概念。我们可以将它理解为一种消息通知的机制,当有一个任务在同步执行的时候,发现它需要去通过某一方式去实现某一个目的(如我们上面提到的去请求一个数据),但这个过程什么时候完成是待定的,那么我们就可以将这一过程(任务)单独放在一边(队列队列),等到主线程(同步任务)执行完毕,再从任务队列中等待继续执行的任务提到主线程中,这整个过程就是事件循环。
如下图,我们来总结下。
当js代码再执行的过程中,会分为同步任务和异步任务,同步任务是按照代码顺序依次执行的任务,而异步任务则是会被放在任务队列中等待执行的任务,再执行的过程中,主线程执行栈会优先执行同步任务,待同步任务执行完后,执行栈会读取任务队列中的回调函数,将回调函数放到主线程执行栈中执行,执行完毕又会继续读取任务队列中的回调函数,放到主线程执行栈中执行,如此往复的过程称为事件循环
宏任务与微任务
在 JavaScript 引擎中,异步任务分为两种类型:微任务和宏任务。
微任务是指在当前任务执行结束后立即执行的任务,它可以看作是在当前任务的“尾巴”添加的任务。常见的微任务包括 Promise 回调和 process.nextTick。
宏任务是指需要排队等待 JavaScript 引擎空闲时才能执行的任务。常见的宏任务包括 setTimeout、setInterval、I/O 操作、DOM 事件等。 JavaScript 引擎会先执行当前任务中的所有微任务,然后再执行宏任务队列中的第一个任务。这个过程会不断重复,直到宏任务队列中的任务被全部执行完毕。
事件循环的应用场景
- DOM 事件处理:通过监听 DOM 事件(例如 click、scroll 等),可以使用事件循环来异步更新 UI 或执行其他操作。
- 定时器:使用 setTimeout() 和 setInterval() 函数可以创建定时器,用于在指定时间间隔之后执行相应的操作。这些操作会被作为异步任务添加到任务队列中等待执行。
- 网络请求:当 JavaScript 需要发送网络请求时,可以使用 XMLHttpRequest 或 fetch API 发送异步请求,并将响应数据作为异步任务加入到任务队列中等待处理。
- Promise 和 async/await:Promise 和 async/await 是 JavaScript 中常用的异步编程方式,实际上它们底层都是基于事件循环机制实现的。通过将回调函数封装为 Promise 对象或 async 函数,可以让异步代码更加易读、易维护。
- Web Workers:Web Workers 可以让 JavaScript 在多线程环境下运行,从而避免阻塞主线程。Web Workers 使用了与事件循环类似的消息队列机制来实现异步通信。