[js日志]宏任务、微任务与Event Loop

1.宏任务和微任务

说到宏任务和微任务,我们就不得不提 Event Loop 了

JS的本质是单线:

  1. 一般来说,非阻塞性的任务采取同步的方式,直接在主线程的执行栈完成。

  2. 一般来说,阻塞性的任务都会采用异步来执行,异步的工作一般会交给其他线程完成,然后回调函数会放到事件队列中。
    在这里插入图片描述
    当主线程的任务执行完了(执行栈空了),JS会去询问事件队列

执行一个宏任务(先执行同步代码)–>执行所有微任务–>UI render–>执行下一个宏任务–>执行所有微任务–>UI render–>…

根据HTML Standard,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行UI render。即:macro-task任务执行完毕,接着执行完所有的micro-task任务后,此时本轮循环结束,开始执行UI render。UI render完毕之后接着下一轮循环。但是UI render不一定会执行,因为需要考虑ui渲染消耗的性能已经有没有ui变动
在这里插入图片描述

2、哪些是宏任务、微任务呢?

2.1 宏任务

在这里插入图片描述
有些地方会列出来UI Rendering,说这个也是宏任务,可是在读了HTML规范文档以后,发现这很显然是和微任务平行的一个操作步骤
requestAnimationFrame姑且也算是宏任务吧,requestAnimationFrame在MDN的定义为,下次页面重绘前所执行的操作,而重绘也是作为宏任务的一个步骤来存在的,且该步骤晚于微任务的执行

2.2 微任务

在这里插入图片描述

2.3 Event-Loop

但是回到现实,JavaScript是一个单进程的语言,同一时间不能处理多个任务,所以何时执行宏任务,何时执行微任务?我们需要有这样的一个判断逻辑存在。
每办理完一个业务,柜员就会问当前的客户,是否还有其他需要办理的业务。(检查还有没有微任务需要处理)
而客户明确告知说没有事情以后,柜员就去查看后边还有没有等着办理业务的人。(结束本次宏任务、检查还有没有宏任务需要处理)
这个检查的过程是持续进行的,每完成一个任务都会进行一次,而这样的操作就被称为Event Loop。(这是个非常简易的描述了,实际上会复杂很多)
而且就如同上边所说的,一个柜员同一时间只能处理一件事情,即便这些事情是一个客户所提出的,所以可以认为微任务也存在一个队列,大致是这样的一个逻辑:

const macroTaskList = [
  ['task1'],
  ['task2', 'task3'],
  ['task4'],
]

for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
    
    
  const microTaskList = macroTaskList[macroIndex]
  
  for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
    
    
    const microTask = microTaskList[microIndex]

    // 添加一个微任务
    if (microIndex === 1) microTaskList.push('special micro task')
    
    // 执行任务
    console.log(microTask)
  }

  // 添加一个宏任务
  if (macroIndex === 2) macroTaskList.push(['special macro task'])
}

// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task

之所以使用两个for循环来表示,是因为在循环内部可以很方便的进行push之类的操作(添加一些任务),从而使迭代的次数动态的增加。

以及还要明确的是,Event Loop只是负责告诉你该执行那些任务,或者说哪些回调被触发了,真正的逻辑还是在进程中执行的。

理解这三者的外部链接

3.任务的优先级

  • 宏任务macrotask:
    主代码块 > setImmediate > MessageChannel > setTimeout / setInterval (大部分浏览器会把DOM事件回调优先处理 因为要提升用户体验 给用户反馈,其次是network IO操作的回调,再然后是UIrender,之后的顺序就难以捉摸了,其实不同浏览器的表现也不太一样,这里不做过多讨论。)

  • 微任务microtask:process.nextTick > Promise = MutationObserver

  • Event Loop 执行顺序如下所示:
    首先执行同步代码,这属于宏任务
    当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
    执行所有微任务
    当执行完所有微任务后,如有必要会渲染页面
    然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

就是说任务执行的顺序是建立与优先级之上的:

如果队列已经有一个setTImeout的宏任务,后来又加入了主代码的宏任务,会让主代码的的任务插队。

4.补充async/await函数

因为,async/await本质上还是基于Promise的一些封装,而Promise是属于微任务的一种。所以在使用await关键字与Promise.then效果类似:

setTimeout(_ => console.log(4))

async function main() {
    
    
  console.log(1)
  await Promise.resolve()
  console.log(3)
}

main()

console.log(2)

复制代码async函数在await之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise时传入的代码,await之后的所有代码都是在Promise.then中的回调

猜你喜欢

转载自blog.csdn.net/u013034585/article/details/106130027