JavaScript是一种单线程的编程语言,意思就是同一时间段只能做一件事,所有任务都需要排队依次完成.
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
看看下面的代码,让我们写出它最后的执行结果
console.log('1')
setTimeout(function (){
console.log('2')
}, 1000)
console.log('3')
/* 运行结果:
1
3
2
*/
啊,js不是单线程吗,它应该从上到下一行一行执行的,只有当上一行的代码执行完后才会执行下一行代码,那应该打印出:1,2,3;那为什么是:1,3,2呢。
JavaScript开发人员意识到,为了不影响主线程正常运行,就把那些耗时的时间(比如定时器,Ajax操作从网络读取数据等)任务挂起来,依次的放进一个任务队列中,等主线程的任务执行完毕后,再回过来去继续执行队列中的任务。
于是,任务就可以分成两种:
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务:不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
执行机制过程如下:
所有同步任务都在主线程上执行,形成一个执行栈(调用栈)。 主线程之外,还存在一个‘任务队列’,浏览器中的各种 Web API 为异步的代码提供了一个单独的运行空间,当异步的代码运行完毕以后,会将代码中的回调送入到任务队列中(队列遵循先进先出得原则)
一旦主线程的栈中的所有同步任务执行完毕后,调用栈为空时系统就会将队列中的回调函数依次压入调用栈中执行,当调用栈为空时,仍然会不断循环检测任务队列中是否有代码需要执行。
其实,这一过程就是我们要了解的event loop(事件循环)机制。
所以打印出来的结果是:1、3、2。
上面说过了同步和异步任务,也了解了event loop(事件循环)机制,大家知道要先等调用栈的同步任务执行完毕后再去执行异步任务。
但是在实际上,异步任务也有区别,分为:macrotask(宏任务) 和 microtask(微任务)。
异步任务既然分为宏任务和微任务,则队列也分为宏任务队列和微任务队列。
当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务。
执行机制过程如下:
第一步: 主线程执行同步任务的同时,把一些异步任务放入‘任务队列’(task queue)中,等待主线程的调用栈为空时,再依次从队列出去任务去执行。
第二步:检测任务队列中的微队列是否为空,若不为空,则取出一个微任务入栈执行;然后继续执行第2步;如果微队列为空,则开始取出宏队列中的一个宏任务执行。
第三步:执行完宏队列中的一个宏任务后,会继续检测微队列是否为空,如果有新插入的任务,这继续执行第二步;如果微队列为空,则继续执行宏队列中的下一个任务,然后再继续循环执行第三步。
总结:js先执行同步任务,再执行异步任务中的微任务,然后是宏任务,如果宏任务中有微任务那执行完微任务之后再执行宏任务。
浏览器中常用的宏任务和微任务:
宏任务 setTimeout 、setInterval 、UI rendering
微任务 promise 、requestAnimationFrame
注意:Promise函数是同步执行后面的.then方法是异步执行
setTimeout(function () {
console.log(3);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(1);
/* 运行结果:
1
2
3
*/