异步方法中的宏任务与微任务
JS的任务事件执行机制:当执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中按顺序获取任务来执行(task-> task-> task…);浏览器为了能够使得 JS 内部 task(任务) 与 DOM 任务能够有序的执行,会在一个 task 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task-> 渲染-> task->…);
宏任务(task):就是上述的 JS 内部(任务队列里)的任务,严格按照时间顺序压栈和执行。如 setTimeOut、setInverter、setImmediate 、 MessageChannel,requestAnimationFrame,I/O等;
微任务(Microtask ):通常来说就是需要在当前 task 执行结束后立即执行的任务,例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。
microtask 队列是一个与 task 队列相互独立的队列,microtask 将会在每一个 task 执行结束之后执行。每一个 task 中产生的 microtask 都将会添加到 microtask 队列中,将会添加至当前 microtask 队列的尾部,并且 microtask 会按序的处理完队列中的所有任务(包括微任务执行中产生的微任务),然后开始执行下一个 task 。
microtask 类型的任务:MutationObserver 、Promise.then()、Promise.catch()、Promise.resolve()、process.next();
简单来说,打比方就是一个银行排队取钱一个一个来(这就是按顺序执行),有人银行卡没带,需要回家拿,但距离有远有近(近的就是微任务,远的就是宏任务),所以先执行微任务队列,再执行宏任务队列;
console.log("script start");
Promise.resolve().then(function(){
console.log("promise1")
})
setTimeout(function(){
console.log("setTimeout")
},0);
Promise.resolve().then(function(){
console.log("promise2")
})
console.log("script end");
//输出
//script start
//script end
//promise1
//promise2
//setTimeout
(1)当前 JS 代码进入主线程被 JS 引擎执行,当前是一个宏任务。按序执行,先输出script start
(2) 接着执行 Promise.resolve(1),该回调进入微任务
(3)执行 setTimeout,回调进入宏任务(这个宏任务是下一个宏任务,而不是当前宏任务)
(4)执行Promise.resolve(2),该回调进入微任务
(5)继续执行,输出script end,当前宏任务执行完毕。检测微任务列表
(6)执行微任务列表,按顺序输出 promise1 promise2
(7)当前微任务列表为空,渲染 DOM 后执行下一宏任务,即 setTimeout 回调函数,输出 setTimeout
console.log('script start');
Promise.resolve().then(function() {
setTimeout(function() {
console.log('setTimeout1');
}, 0);
}).then(function() {
console.log('promise1');
});
setTimeout(function() {
console.log('setTimeout2')
Promise.resolve().then(function(){
console.log('promise2');
})
},0)
console.log('script end');
//输出:
//script start
//script end
//promise1
//setTimeout2
//promise2
//setTimeout1
(1)进入宏任务,执行当前代码,输出 script start
(2)执行 Promise.resolve(1),回调(内含函数 setTimeout(1))进入微任务
(3)执行 setTimeout(2),回调(内含函数 Promise.resolve(2)) 进入下一宏任务
(4)输出 script end,当前宏任务结束。
(5)宏任务结束,查看微任务队列,当前微任务是 Promise.resolve(1) 回调,执行回调里面的 setTimeout(1),定时器回调进入宏任务,接着输出 promise1,当前微任务为空
(6)当前微任务为空,执行下一宏任务。当前宏任务是 setTimeout(2)回调,输出 setTimeout2,执行回调里面的 Promise.resolve(2),回调进入微任务,当前宏任务结束
(7)当前宏任务结束,执行微任务。输出 Promise2,微任务为空。
(8)执行下一宏任务,setTimeout1 回调输出 setTimeout1
注意:new Promise()是同步方法,优先于宏任务和微任务,Promise.resolve()/Promise.reject()/Promise.then()才是异步微任务;
Promise.resolve().then(function(){ //异步微任务
console.log("promise1")
})
new Promise(function(resolve,reject){ //同步
console.log(2);
for(var i=0;i<10;i++){
i==9&&resolve();
}
console.log(3)
}).then(function(){//异步微任务
console.log(4)
});
console.log(5);//同步
//输出
//2
//3
//5
//promise1
//4总结就是:先同步任务>异步任务(微任务>宏任务)
!!!!!!!Node和浏览器的执行顺序有所出入:浏览器环境下,如果有多个宏任务(宏1<包含微任务1-1>、宏2<微任务2-1>),执行顺序:宏1->微1-1->宏2->微2-1,会把当前宏任务中的微任务执行完再去执行下一个宏任务。
node (v11.0-) 环境里面是遇到微任务不执行,把它入微任务队列,把本次执行周期内所有宏任务走完,再执行微任务队列中的任务;
!!!!最近刚看到,node的V11+版本已经做了修改,和浏览器一样!!!!
EventLoop浏览器环境和Node不同版本环境
Tick1(事件循环周期1):先同步任务,遇到微任务,入队列(宏任务同理),当前栈清空;执行微任务队列中任务;
Tick2:
浏览器环境:遇到then(100)入微任务队列,直接执行,遇到setTimeout(555),入宏任务队列等待Tick3执行,执行setTimeout(3)宏任务,Tick2结束,输出:2 promise 3。
node v11- 版本环境:遇到这个then(100)入微任务队列,暂时不执行(等待Tick执行),宏任务同理;接着执行setTimeout(3)宏任务,Tick2结束,输出2 3。
Tick3:执行setTimeout(555),setTimeout(666)
// 这个列子里面,包含了宏任务,微任务,分别看看浏览器和node 打印的结果
//(整体是Tick1)
console.log(1)
// Tick2
setTimeout(function(){
console.log(2)
//浏览器与Node(v11.0.0-)分歧点
// 微任务
Promise.resolve().then(function(){
console.log('promise')
});
//Tick3
setTimeout(function(){
console.log(555)
});
})
// Tick2
let promise = new Promise(function(resolve, reject){
console.log(7)
resolve()
}).then(function(){
// 微任务
console.log(100)
})
// Tick2
setTimeout(function(){
console.log(3);
//Tick3
setTimeout(function(){
console.log(666)
});
})
console.log(5)
// 浏览器结果:1 7 5 100 2 promise 3 555 666
// node 结果: 1 7 5 100 2 3 promise 555 666Dom操作到底是同步,还是异步?
这里出现一个说不清道不明的疑问,Dom操作到底是同步操作还是异步操作?
如果是同步操作,那vue的nextTick方法是做什么用的?不就是在Dom更新完之后的回调方法吗?
如果是异步操作,那在剧烈操作Dom后面的代码,为什么会被阻塞?而且代码看上去,也的确是按顺序执行的?
这里直接说明:js里面的Dom操作代码,是同步执行,但浏览器进行的Dom渲染,是异步操作。浏览器渲染Dom和执行js,同时只能二选一,渲染一次Dom的时机是,当前宏任务和小尾巴微任务执行完,下一个宏任务开始前
vue的nextTick方法,则是使用H5的Api---MutationObserver,监听浏览器将Dom渲染完成的时机。
若浏览器不支持此方法,则会使用setTimeout,把nextTick回调函数的执行时机,作为一个宏任务;
上面也说了,浏览器渲染一次Dom,是下一个宏任务开始前,这样使用了setTimeout,保证了Dom确实渲染完成。
这里也需要稍作提醒,js操作Dom是同步的,但操作Dom,毕竟超出了js本身语言的Api,每操作一次Dom,都需要
消耗一定的性能,所以,在适合的情况下,最好先把要修改的Dom的内容,以字符串或者虚拟Dom的形式拼接好,
然后操作一次Dom,把组装好的Dom字符串或虚拟Dom,一次性的塞进HTML页面的真实Dom中。async和await 在浏览器和11以下node环境中,执行顺序也不一样;
















