目录
2、我们在工作常用到的宏任务是 setTimeout,而微任务是 Promise.then
一、js 单线程
众所周知,JavaScript是单线程的,也就是只有一个调用栈,调用栈按照先入后出的规则进行。一次调用一个,而且可以嵌套。
在ES5之后,JavaScript引入了Promise,这样不需要浏览器,JavaScript引擎自身也能够发起异步任务了。所以,js是可以执行同步和异步任务的。
在执行调用栈的时候,会先执行同步任务(同步任务是不需要等待的任务)。调用栈在发现异步任务的时候,会把异步任务放入队列中,异步任务队列分为宏任务队列和微任务队列。(队列都按照先入先出的规则)
同步的任务众人皆知是按照顺序去执行的;
而异步任务的执行,是有一个优先级的顺序的,包括了宏任务(macrotasks)和 微任务(microtasks)。
可以把宏任务理解成硬菜,微任务理解成软菜。一般是先上软菜,再上硬菜。
二、什么是宏任务?
1、定义
宏任务:指消息队列中的等待被主线程执行的事件
宏任务执行时都会重新创建栈,然后调用宏任务中的函数,栈也会随着变化。但宏任务执行结束时,栈也会随之销毁。宏任务是由宿主(浏览器/Node)发起的。
2、程序中哪些会被归为宏任务队列?
整体代码script,setTimeout,setInterval ,setImmediate,Promise*本身 ,I/O,
UI rendering , requestAnimationFrame 等~~
3、举例:
setTimeout(function(){
console.log('1')
});
三、什么是微任务?
1、定义
微任务:可以看成是一个需要异步执行的函数
微任务是基于消息队列、事件循环、UI 主线程还有堆栈而来的。微任务是由JS引擎发起的。
微任务的执行时机:在主函数执行结束之后、当前宏任务结束之前
2、程序中哪些会被归为微任务队列?
Promises.(then catch finally),Async/Await ,Object.observe ,
MutationObserver , process.nextTick(node)
3、举例:
new Promise(function(resolve){
console.log('2'); // 主线程
resolve();
}).then(function(){
console.log('3') // 微任务
});
四、宏任务和微任务的区别
宏任务和微任务的区别在于在事件循环机制中,执行的机制不同
每次执行完所有的同步任务后,会在任务队列中取出异步任务,先将所有微任务执行完成后,才会执行宏任务
所以可以得出结论:
1、微任务会在宏任务之前执行。
主线程任务 => 异步任务(微任务 => 宏任务)
2、我们在工作常用到的宏任务是 setTimeout,而微任务是 Promise.then
3、注意这里的Promise().then()
new Promise在实例化的过程中所执行的代码是同步的,而在 then中注册的回调函数才是异步的!!!
new Promise(function(resolve){
console.log('1'); // 主线程任务
resolve();
}).then(function(){
console.log('2') // 微任务
});
五、执行顺序
如何运行就需要靠Event Loop事件循环:
① 在调用栈中先执行同步任务
期间,遇到宏任务就将其加入宏任务队列,遇到微任务就将其加入微任务队列。
② 在同步任务执行结束之后,调用栈会被清空,事件循环优先执行微任务
(也就是说,每次宏任务结束之后事件循环就会先执行微任务),直到微任务队列里面的任务被清空才会执行下一轮宏任务(并不会把宏任务全部执行了再执行微任务,这样子就可以避免因为宏任务繁重导致任务的阻塞)。
③ 微任务队列被清空的时候,不会立即执行宏任务,会优先看下浏览器是否需要渲染,渲染后再执行宏任务。
script此时也退出了宏任务,因为setTimeout其实是要等到script结束后才会执行回调,所以前面有个alert的时候,浏览器的渲染任务会被卡住,setTimeout也不会执行回调。
④ 如果此时宏任务的其中一轮结束后,也就是调用栈清空的时候,事件循环又发现了微任务。还会执行微任务,并且清空微任务队列,看下是否需要渲染,渲染完之后事件循环再执行下一轮宏任务,在没有其余微任务的时候,宏任务就可以一直清空宏任务队列,这就是整个事件循环流程。
六、程序题例子
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});
console.log('4');
new Promise(function(resolve){
console.log('5');
resolve();
}).then(function(){
console.log('6')
});
setTimeout(function(){
console.log('7')
});
function bar(){
console.log('8')
foo()
}
function foo(){
console.log('9')
}
console.log('10')
bar()
解析:
首先浏览器执行Js代码由上至下顺序,遇到setTimeout,把setTimeout分发到宏任务Event Queue中
new Promise属于主线程任务,直接执行 => 打印2
Promis下的then方法属于微任务,把then分到微任务 Event Queue中
console.log(‘4’)属于主线程任务,直接执行 => 打印4
又遇到new Promise,直接执行 => 打印5Promise 下到then分发到微任务Event Queue中
又遇到setTimouse也是直接分发到宏任务Event Queue中,等待执行
console.log(‘10’)属于主线程任务,直接执行 => 打印10
主线程内容:遇到 bar() 函数调用,执行构造函数内的代码 => 打印8
在bar函数中调用foo函数,执行foo函数内的代码 => 打印9
主线程中任务执行完后,就要执行分发到微任务Event Queue中代码,实行先进先出 => 依次打印3,6
微任务Event Queue中代码执行完,就执行宏任务Event Queue中代码,也是先进先出 => 依次打印1,7。
最终结果:2,4,5,10,8,9,3,6,1,7