事件循环,宏任务与微任务

54 阅读3分钟

浏览器中的JavaScript线程

我们经常会说JavaScript是单线程(可以开启workers)的,但是JavaScript的线程应该有自己的容器进程浏览器或者Node

Js是单线程

  • js是单线程的。也就是说,同一时间只能做一件事。(有些任务是耗时的,会阻塞代码的执行)
  • 作为浏览器脚本语言,与它的用途有关。
  • js的主要用途是和用户互动,以及操作DOM。这决定了它只能是单线程。

同步、异步

可以把代码分为同步代码(同步任务)和异步代码(异步任务)

  • 同步代码:立即放入js引擎(js主线程)执行,并原地等待结果。

  • 异步代码:先放入宿主环境(浏览器/Node),不必原地等待结果,并不阻塞主线程继续往下执行,异步结果在将来执行。比如setTimeout、setInterval、Ajax/Fetch、事件绑定等。

浏览器的线程

浏览器是一个进程吗,它里面只有一个线程吗?

  • 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;

  • 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;

    由于JavaScript的代码执行是在一个单独的线程中执行的,这意味着JavaScript的代码,在同一个时刻只能做一件事; 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;所以真正耗时的操作,实际上并不是由JavaScript线程在执行的,因为我们的浏览器每个进程是多线程的,那么其他线程可以完成这个耗时的操作。 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可

    如果在执行JavaScript代码的过程中有异步操作,比如中间我们插入了一个setTimeout函数调用。这个函数被放到调用栈中,执行会立即结束,并不会阻塞后续代码的执行。

image.png

  function sum(){
     .....
  }
  
  setTimeout(()=>{
     console.log('xxx')
  },1000)
  
  
  other code....
  • Js是单线程,防止代码阻塞,把代码(任务)分为:同步和异步

  • 同步代码给js引擎执行,异步代码交给宿主环境

  • 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队

  • 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程就是事件循环(eventloop)

宏任务与微任务

但是事件循环中并非只维护着一个队列,事实上是有两个队列, 异步的代码才有宏任务与微任务。

  • 宏任务队列(macrotask queue):ajaxsetTimeoutsetIntervalDOM监听、UI Rendering等

  • 微任务队列(microtask queue):Promise的then回调、** Mutation Observer API**、**queueMicrotask()**等

那么事件循环对于两个队列的优先级是怎么样的呢?

1.main script中的代码优先执行(编写的顶层script代码);

2.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行

也就是宏任务执行之前,必须保证微任务队列是空的;

如果不为空,那么就优先执行微任务队列中的任务(回调)