JS基础篇:2、任务队列的执行顺序

893 阅读4分钟

上一篇讲述了什么是任务队列,这一篇就来讲讲任务队列的执行顺序

# 同步与异步

通过上一篇我们知道JS是单线程语言,所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。 这种设计有好处也有坏处!好处肯定是能提高效率,坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。 为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步任务异步任务

同步任务就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;

异步任务异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务

var t1 = setTimeout(function(){
     console.log(2);
 },0);
 
console.log(1);

var t2 = setTimeout(function(){
     console.log(3);
 },0);

我们通过这俩篇文章的学习能清楚的知道上面打印的顺序 是 1、2、3。因为setTimeout是异步任务,而t1又比t2先注册,所以最终输出了这个结果。

然而,仅仅通过以上代码我们确定不了同步任务究竟是不是会优先于异步任务执行,因为setTimeout有一个最小的时间间隔限制,在这个时间间隔里语句console.log(1)完全可以执行完毕,我们要想办法让同步代码占用更长时间。

setTimeout(function() {
  console.log(1);
}, 0);
 
console.log(2);
 
let end = Date.now() + 1000*3;
 
while (Date.now() < end) {}
 
console.log(3);
 
end = Date.now() + 1000*3;
 
while (Date.now() < end) {}
 
console.log(4);

控制台打印出结果:2、3、4、1。从上面的输出结果我们可以确定,异步代码是在所有同步代码执行完毕以后才开始执行的。

宏任务与微任务

什么是宏任务:(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
什么是微任务:microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

常见微任务 promise.then、process.nextTick、MUtationObserver

常见宏任务 setTimeout、setInterval、setImmediate、MessaageChannel

我们看下面代码

setTimeout(function(){
    console.log(1);
},0);
 
new Promise(function(resolve){
    console.log(2);
    resolve();
    console.log(3);
}).then(function(){
    console.log(4);
});
 
console.log(5);
 
setTimeout(function(){
    console.log(6);
},0);
 
console.log(7);

控制台打印结果2、3、5、7、4、1、6。怎么解释这个结果呢?首先我们知道同步任务肯定是优先于异步任务的,setTimeout 异步任务肯定在同步任务结束后才执行, 定时时间相同注册早的先执行 所以肯定1在6前面,至于为什么先打印出2是因为promise内部是属于同步任务,根据js从上到下执行的顺序 所以优先执行,那执行到resolve时候为什么没有先打印4呢 是因为resolve(.then)属于微任务,微任务执行顺序是在当前宏任务执行完毕才会执行微任务,所以这个时候给他放入微任务队列中,继续向下执行打印3、5、7同步任务到这里执行结束开始执行当前微任务队列所以打印4,最后执行异步任务打印1、6。 微任务是特殊的异步任务,它和异步任务区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。

结合上述实例我们来了解JS的运行机制

运行机制

事件循环大致分为以下几个步骤

  1. 当js代码执行时,所有同步的任务都在主线程上执行,形成一个执行栈
  2. 在主线程之外还有一个任务队列(task queue),只要异步任务有了运行结果就在任务队列中放置一个事件;
  3. 一旦执行栈中所有同步任务执行完毕(主线程代码执行完毕),此时主线程不会空闲而是去读取任务队列。此时,异步的任务就结束等待的状态被执行。
  4. 主线程不断重复以上的步骤。