JS 事件循环怎么处理宏任务和微任务?

前言

我们知道JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。
所谓单线程是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。就是下面的讨论的同步和异步的概念。

同步

同步:同步,是指代码从上向下执行,执行完一条,才去执行下一条,是按照顺序按照步骤的执行。比如,如果直接加载js文件,首先加载第一个文件a.js,并且执行这个js文件,完成后在加载b.js。这叫做同步:同步是在渲染DOM之前做的

异步

异步,代码执行需要有一个过程,或者需要一定的时间,或者开始的时间不确定,这时候
我们先让别的不相关的代码执行,而当前代码当执行完成后去执行一个回调函数。

js为什么需要异步?

如果JS中不存在异步,只能自上而下执行,万一上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。

下面我们来看一段代码

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');

不用我说也知道让你判断打印顺序是吧。那是什么呢?

正确答案是

为什么会出现这样的打印顺序?

要理解这些首先需要对事件循环机制处理宏任务和微任务的方式有所了解
每个线程都会有它自己的event Loop(事件循环),所以能够独立运行。然而所有的同源窗口会共享一个eventLoop以进行通信。event Loop会一直运行,来执行进入队列的宏任务。一个event Loop会宏任务源,这些宏任务源保证了本任务源内的顺序。但是浏览器每次都会选择源中的一个宏任务去执行。这保证可浏览器给与一些宏任务(如用户输入)以更高的优先级

宏任务(task)

浏览器为了能够使得JS内部Task与DOM任务能够有序的执行,会在一个task执行完毕结束后,在下一个task执行开始前,对页面进行重新渲染(render)
task->rander->task

鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTML.还有下面这个setTimeOut,setTimeOut的作用是等待给定的时间后回调产生一个新的宏任务。这就是为什么打印“setTimeOut”在打印“script end” 之后,因为打印“script end”是第一个宏任务里面的事情,而setTimeout 是更一个独立的任务里面的打印的。
异步有:setTimeout setInterval

微任务(Microtasks)

微任务通常来说就是在当前task执行结束后立即执行的任务,比如对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的task,这样便可以减小一点性能的开销。只要执行栈中没有其他JS代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列中加入了新的微任务,就会把这个新的微任务加入到队列的尾部,之后也会被执行。微任务包括了promise async await。
一旦一个promise有了结果,或者早已有了结果(有了结果是指这个promise到了rejected状态),他就会为他的回调产生一个微任务,这就保证了回调异步执行的即使这个promise有了结果。所以对一个已经有了结果的promise调用.then()方法会立即产生一个微任务。这就为什么“promise1”,"promise2"会打印在“script end”之后,因为所有微任务执行的时候,当前执行栈的代码必须执行完毕。"promise1","promise2"会打印在"setTimeOut"之前,是因为所有的微任务总会在下一个宏任务之前全部执行完毕!
微任务有:promise async await

所以,我们用一张图来看任务的处理过程

虽然JS是单线程的但是浏览器的内核是多线程的,在浏览器的内核中不同的异步操作由不同的浏览器内核模块调度执行,异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。

最后我们总结一下运行机制:

  • 同一个队列中,先执行的是宏任务,再执行其他任务,最后执行微任务
  • 在当前队列中出现的异步,如果是微任务就会放在当前任务队列最底端, 如果当前队列出现的异步是宏任务,就会出现在下一个队列最顶端。
  • 同一个队列中触发异步,微任务先执行,宏任务后执行
posted @ 2020-04-28 19:24  码上出彩  阅读(1520)  评论(0编辑  收藏  举报