导读
javascript是一门单线程语言,一切javascript版的多线程都是用单线程模拟出来的,所以代码执行还是顺序执行的原则,只不过编写的顺序被执行环境重新“编排”了一下而已。
执行过程中,我们把任务分为同步任务和异步任务,而异步任务又分为宏任务,微任务。
一般顺序:同步->微任务->宏任务
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
node环境与浏览器环境区别:(这里先写出区别,下面做详细介绍,具体不同环境为什么异步输出不同。)
node环境中,任务全部出队,并执行。
浏览器环境,出队一个任务,并执行。
任务入队规则
异步任务队列分为宏任务队列,微任务队列。
代码顺序执行,碰到异步代码后放入对应队列内。第一遍代码执行完毕后,会取出微任务队列内的代码继续执行,执行完后再取出宏任务队列内的代码继续执行。以此反复执行直到最后执行完所有任务。
运用递归思想描述
function 代码执行(任务){
任务执行,异步的代码入队
代码执行(微任务出队)
代码执行(宏任务出队)
}
这里说明一下:由于node环境下的事件监听依赖libuv与前端环境不完全相同,输出会有差异。
主要原因:虽然规则相同,但是入队的顺序会有差异,出队任务数量也有不同,所以输出会有差异。
实例演示
下面结合实例,将在node端和浏览器端分别说明其差异性。
使用代码:
console.log('1');
setTimeout(function() { //timeout1
console.log('2');
new Promise(function(resolve) {
console.log('4');
setTimeout(function(){//timeout2
console.log('5');
resolve()//promise1
})
}).then(function() {
console.log('6');
})
})
new Promise(function(resolve) {
console.log('8');
resolve();//promise2
}).then(function() {
console.log('9');
})
setTimeout(function() { //timeout3
console.log('10');
new Promise(function(resolve) {
console.log('12');
resolve();//promise3
}).then(function() {
console.log('13');
})
setTimeout(function(){//timeout4
console.log('14')
new Promise(function(resolve){
console.log('15')
resolve() //promise4
}).then(function() {
console.log('16');
})
})
})
浏览器端:
这里建议打开两个页面,一边对照代码一边看一下步骤。
第一次执行代码:输出1,timeout1压入宏队列,输出8,promise2压入微队列,timeout3压入宏任务队列。
//在浏览器端,边执行代码,边把异步代码加入对应队列,这一次执行都是同步代码。
//宏任务队列内[timeout1,timeout3],微任务队列[promise2]
第二次执行代码(promise2出队):输出9
//微任务队列优先级高,先执行微任务,浏览器端只有一个任务出队并执行。
//宏任务队列内[timeout1,timeout3],微任务队列[]
第三次执行代码(timeout1出队):输出2,输出4,timeout2压入宏队。
//宏任务队列内[timeout3,timeout2],微任务队列[]
第四次执行代码(timeout3出队):输出10,输出12,promise3压入微队列,timeout4压入宏队列。
//宏任务队列内[timeout2,timeout4],微任务队列[promise3]
第五次执行代码(promise3出队):输出13。
//宏任务队列内[timeout2,timeout4],微任务队列[]
第六次执行代码(timeout2出队):输出5,promise1压入微队列。
//宏任务队列内[timeout4],微任务队列[promise1]
第七次执行代码(promise1出队):输出6。
//宏任务队列内[timeout4],微任务队列[]
第八次执行代码(timeout4出队):输出14,输出15,promise4压入微队列。
//宏任务队列内[],微任务队列[promise4]
第九次执行代码(promise4出队):输出16。
//宏任务队列内[],微任务队列[]
顺序为:1,8,9,2,4,10,12,13,5,6,14,15,16
node端
第一次执行代码:输出1,timeout1压入宏队列,输出8,promise2压入微队列,timeout3压入宏任务队列。
//第一次执行node端和浏览器端二者相同。
//宏任务队列内[timeout1,timeout3],微任务队列[promise2]
第二次执行代码(promise2出队):输出9
//二者相同。
//宏任务队列内[timeout1,timeout3],微任务队列[]
第三次执行代码([timeout1,timeout3出队):输出2,输出4,timeout2压入宏队列,输出10,输出12,promise3压入微队列,timeout4压入宏队列。
//这里我们可以看到,node与浏览器不同之处,timeout1,timeout3一起出队,所以内部的同等级(同步代码)就一起执行了。node环境的这一次执行完成了浏览器多次执行步骤。
//宏任务队列内[timeout2,timeout4],微任务队列[promise3]
第四次执行代码(promise3出队):输出13。
//宏任务队列内[timeout2,timeout4],微任务队列[]
第五次执行代码(timeout2,timeout4出队):输出5,promise1压入微队列,输出14,输出15,promise4压入微队列。
//在这里输出数值与浏览器端开始有差异。
//宏任务队列内[],微任务队列[promise1,promise4]
第六次执行代码(promise1,promise4出队):输出6,输出16。
//宏任务队列内[],微任务队列[]
顺序为:1,8,9,2,4,10,12,13,5,14,15,6,16
总结:
简单结构的代码,一般执行顺序:同步->微任务->宏任务。复杂结构代码,微任务优先级大于宏任务优先级。同步代码边执行,边把异步任务入队,然后系统选择出队的任务再一次新一轮执行。
node环境与浏览器环境区别:node环境中,任务全部出队,并执行。浏览器环境,出队一个任务,并执行。
备注1
console.log("11")
new Promise(function(resolve) {
console.log('12');//这里代码属于同步代码,只有then内代码才是异步代码,同上面“console.log("11")”代码段一起顺序执行。
resolve();
}).then(function() {
console.log('13');
})
备注2
在node环境中process.nextTick()比Promise优先级高,所以优先执行
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
process.nextTick(function() {
console.log('6');
})
//7,6,8
备注3
这一次,彻底弄懂 JavaScript 执行机制:https://juejin.im/post/59e85eebf265da430d571f89