浅谈JavaScript中的异步运行机制

294 阅读4分钟

不知道大家在看面试题的时候,是不是会经常看到一写问你打印顺序的题。类似下面这种

setTimeout(() => {
    console.log('1')
}, 0)
console.log('2');
new Promise((resolve) => {
    console.log('3');
    resolve()
}).then(() => {
    console.log('4');
}).then(()=>{
    console.log('5')
})

相信这种题对大家来说都是信手拈来的了。但有的时候我就在想,为什么他就是这么执行的呢?很想去了解一下背后的执行机制。经过我的一番学习,今天就想和大家一起分享,探讨一下关于这方面的一下知识。写的不好或者有错漏的地方,也非常欢迎大家指正。

从这个题来看,相信大家也都知道打印的顺序,也知道setTimeoutPromise都是耗时任务,肯定不会顺着执行,但是是setTimout 先执行呢还是Promise先执行呢?也有很多人知道setTimout 是宏任务 Promise中的 then() 是微任务。说了这么多那是宏任务和微任务,为什么微任务比宏任务先执行?我们来一起探讨一下。

宏任务Macro Task 和 微任务Micro Task

宏任务和微任务只不过是 JavaScript 中异步任务的两个分类而已。我们要知道是宏任务和微任务两者有什么本质的区别。下次在看到这种问题,呸,以后他都不配称之为问题了。当然,只是开个玩笑,只能说在日常的工作中,可能对这些问题,处理起来更有信心和把握。

其实你会发现,常见的微任务Promiseasync/await,常见的宏任务:setTimeoutsetIntervalonClick等 DOM 操作,有木有嗅到一丝丝不是一个级别的东西,setTimeoutsetInterval 这些好像是浏览器规定的。而 Promiseasync/await 貌似就是ES6+ 语法的一些规定。像这些微任务其实会存放在一个叫 Micro Task Queen 里面,而宏任务会放在 Web APIs 里面。而他们的执行,会先执行 Micro Task Queen 中的任务,然后再执行 Web APIs 里的任务。

我想到这里,我们就应该算是比较深的层面了解了为什么宏任务会比微任务慢执行了吧。

那宏任务和微任务是和 DOM 渲染的执行顺序是怎么样的呢?我们借助下面一段代码来感受一下吧:

var container = $('#container');
const $p1 = $('<p>HTML5</p>'),
      $p2 = $('<p>JavaScript</p>'),
      $p3 = $('<p>CSS3</p>');

container.append($p1)
         .append($p2)
         .append($p3)
const childsLength = $('#container').children().length;

Promise.resolve().then(() => {
    console.log('微任务': childsLength);
    alert('请注意看页面的DOM元素是否已经渲染')
})

setTimeout(() => {
    console.log('宏任务': childsLength);
    alert('请注意看页面的DOM元素是否已经渲染')
}, 0)

Event Loop 和 DOM 渲染

众所周知 javaScript 是单线程的,而且他和 DOM 渲染共用一个线程。JavaScript 在执行的时候,是需要留事件给浏览器进行 DOM 渲染的。那他是怎么实现这种调度的呢?我们可以尝试去理解一下他的思路:

  1. 执行同步代码,在遇到异步的回调函数时,会把异步的回调放到一个叫 callBack Stack 的栈中

  2. 当全部的同步代码执行完以后,就是一个轮询了。一个轮询以后,线程会执行 callBack Stack 中的回调函数。

  3. callBack Stack 中的回调函数执行完成以后,会执行 micro task queue 中的微任务

  4. 浏览器会尝试执行 DOM 渲染

  5. 执行 web APIs 中的宏任务

  6. 最后再次触发 Event Loop

为什么说是尝试执行 DOM 渲染呢?因为 callback 函数执行以后,不一定会有 DOM 的变化,也就不一定需要去执行 DOM 渲染。如果说,上面说的有点不好理解的话,也可以回看上面那段简单的代码来尝试体会一下这个过程。

小结

  • 宏任务是在 DOM 渲染之后 才会触发执行的,如 setTimeout,微任务是在 DOM 渲染之前 才会触发执行的,如 Promise
  • 常见的宏任务:setTimeoutsetIntervalAjaxDOM 事件
  • 常见的微任务:Promiseasync / await

当然,可能还有很多东西没有写到和说明白,欢迎各位大牛批评指正。若后面发现更多内容,也会进行改正和更新的。