一篇文章让你彻底搞懂,什么是JavaScript执行机制!( 二 )


我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?
答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行 。举例说明:
//代码1console.log('先执行这里');setTimeout(() => {console.log('执行啦')},0);//代码2console.log('先执行这里');setTimeout(() => {console.log('执行啦')},3000);代码1的输出结果是:
//先执行这里//执行啦代码2的输出结果是:
//先执行这里// ... 3s later// 执行啦关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的 。根据HTML的标准,最低是4毫秒 。有兴趣的同学可以自行了解 。
4.又恨又爱的setInterval上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval 。他俩差不多,只不过后者是循环的执行 。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待 。
唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue 。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了 。这句话请读者仔细品味 。
5.Promise与process.nextTick(callback)传统的定时器我们已经研究过了,接着我们探究Promise与process.nextTick(callback)的表现 。
Promise的定义和功能本文不再赘述,而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数 。
我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick
不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue 。
事件循环的顺序,决定js代码的执行顺序 。进入整体代码(宏任务)后,开始第一次循环 。接着执行所有的微任务 。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务 。听起来有点绕,我们用文章最开始的一段代码说明:
setTimeout(function() {console.log('setTimeout');})new Promise(function(resolve) {console.log('promise');}).then(function() {console.log('then');})console.log('console');
  • 这段代码作为宏任务,进入主线程 。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue 。(注册过程与上同,下文不再描述)
  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue 。
  • 遇到console.log(),立即执行 。
  • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行 。
  • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始 。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行 。
  • 结束 。
事件循环,宏任务,微任务的关系如图所示:
一篇文章让你彻底搞懂,什么是JavaScript执行机制!

文章插图
 
我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:
console.log('1');setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})})process.nextTick(function() {console.log('6');})new Promise(function(resolve) {console.log('7');resolve();}).then(function() {console.log('8')})setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})})第一轮事件循环流程分析如下: