一文搞懂Event Loop

JS是一门单线程语言,那为何能够实现异步操作呢?

前言

JS是一门单线程语言,那为何能够实现异步操作呢?
单线程和异步操作确实不能同时成为一个语言的特性。JS本身不能实现异步,但是JS的宿主环境(浏览器,Node)是多线程的,宿主环境通过某种方式,使得JS具备了异步的特性。

必备概念

堆(Heap),栈(Stack)、队列(Queue)

  • 堆(Heap)
    堆是一种数据结构,是利用完全二叉树维护的一组数据.
  • 栈(Stack)
    栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表.后进先出(LIFO) 图片
  • 队列(Queue) 图片

Event Loop按任务分类

  • 宏任务
  • 微任务

Event Loop分类

  • 浏览器事件循环
  • node事件循环

浏览器事件循环

JS是单线程语言,浏览器只分配给JS一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。
浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

任务队列

浏览器为这些异步任务单独开了一个线程,那么主线程是如何知道异步任务是否已经完成呢?这就需要依赖回调函数了,整个程序是靠事件驱动的,每个事件都有相应的回调函数。

1
2
3
setTimeout(function(){
console.log("time over")
}, 100)

主线程

JS一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。

图片

  • 浏览器为异步任务开启的线程序=>WebAPIs
  • 任务队列=>callback queue
  • 主线程
    • 堆和栈
    • 函数的执行就是通过进栈和出栈实现
    • 栈stack清空时,说明一个任务已经执行完成,这时会从callback queue中寻找下一个任务推入栈中

浏览器执行流程

  1. 先执行主线程代码(js引擎),主线程中非宏任务和非微任务的都是同步代码。
  2. 主线程执行过程中遇到宏任务推到任务队列中,主线程同步代码执行完后,开始从队列中取出(先进先出)宏任务执行
  3. 碰到微任务push到栈中
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');