一招解决宏任务、微任务、Event Loop概念

246 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

首先我们先来介绍几个概念:

1.进程和线程

线程(Thread)是操作系统能够进⾏运算调度的最⼩单位。它被包含在进程之中,是进程中的实际运作单位。⼀条线程指的是进程中⼀个单⼀顺序的控制流,⼀个进程中可以并发多个线程,每条线程并⾏执⾏不同的任务。

进程(Process)是操作系统分配资源的基本单位,⼀个进程拥有的资源有⾃⼰的堆、栈、虚存空间、⽂件描述符等信息。 从编程的⻆度来理解进程,可以把它看作是⼀个类或⼀个 PCB(Process Control Block)进程控制块的结构体。

它们都是对都是对CPU工作时间片的描述、进程描述的是CPU在运行指令及加载和保存上下文所需要的时间、线程描述的是执行一段指令所需要的时间。

2.JavaScript语言

我们常用的JavaScript就是一个单线程的脚本语言,也就是说我们在执行代码的过程中不会出现同时进行两个线程(执行两段代码),它只能按照HTML的顺序从上到下执行。这样有两个好处:第一节节省内存,节省上下文切换的时间、第二不会和渲染线程冲突

3.执行栈

用一句话来描述它就是:v8内部维护出来一个用来存放函数的执行上下文环境的一个栈结构

4.同步代码

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。同步的处理过程为:提交请求->等待服务器处理(这个期间浏览器不能执行其他代码)->处理完毕返回

5.异步代码

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。代码在执行的过程中,遇到异步代码,会将异步代码用队列装起来,实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。异步的处理过程为:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕返回

宏任务

(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程为:(macro)task->渲染->(macro)task->......

宏任务包含:

  • I/O
  • UI-rendering (页面渲染)
  • script
  • setTimeout
  • setInterval
  • setImmediate (Node.js环境)
  • requestAnimationFrame (在浏览器环境)

微任务

microtask,可以理解是在当前task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

微任务包含:

  • Promise.then
  • Object.observe
  • MutationObserver
  • process.nextTick(Node.js 环境)

Event Loop(事件循环)

代码的执行顺序:

  1. 首先执行同步代码,这属于宏任务;
  2. 当执行完所有的同步代码后,执行栈为空,检查是否有异步代码要执行;
  3. 执行微任务;
  4. 执行完微任务后,有必要的情况下会渲染页面;
  5. 开启下一轮 Event Loop,执行宏任务中的代码;

在Event Loop事件循环中,我们的代码执行基本就是按照上述顺序执行。下面我们来段代码来分析分析事件循环。

console.log('start');  // 1
function foo() {
  console.log('foo');  // 2
}
foo()
setTimeout(function() { // 7 异步宏任务
    console.log('setTimeout');
  }, 0)
  new Promise(resolve => {
    console.log('Promise');  // 3
    resolve()
  })
    .then(function() {
      console.log('promise1');  // 5 //异步微任务
    })
    .then(function() {
      console.log('promise2');  // 6  //异步微任务
    })
console.log('end');   //4

微信图片_20220721115836.png 我们来从上到下分析上面这段代码,首先我们遇到同步代码console.logfoo打印出startfoo,再向下遇到异步宏任务代码setTimeout,我们将其挂起,在向下我们遇到同步代码Promise打印出Promise,在向下我们遇到了Promise.then异步微任务,我们也将其挂起。再向下我们遇到了同步代码console.log打印出end。到这里,我们的同步代码就执行完了,接下我们就要执行我们挂起的异步代码了,我们首先执行异步微任务promise.then打印出promise1promise2。之后我们再执行异步宏任务setTimeout打印出setTimeout,这样代码就执行完了。

这里有一个小细节就是定时器setTimeout的时间不会影响代码的执行,因为定时器的时间存在误差。

小结

以上就是我对JS中宏任务、微任务和Event Loop概念的理解,欢迎大家阅读和指正!