有以下demo,最终控制台会输入的内容是啥
setTimeout(()=>{
console.log("timer1")
Promise.resolve(1).then(()=>{
console.log("promise1")
})
console.log("timer 1normal")
})
setTimeout(()=>{
console.log("timer2")
})
console.log("primary")
- 1,第一个是primary,这个没有疑问,住流程的代码先执行嘛
- 2,然后是timer1
- 3,再然后是timer 1normal,
- 4,,接着是promise1
- 5,timer2
为什么1比2快呢?
因为1是主流程代码,2是宏任务里面的代码啊🤔
那为什么3比4快呢?
因为3是主流程代码啊,4是微任务里面的代码🤔
好像这样开始有点迷茫了~~,3和4不是都在宏任务里面的么。
宏任务队列和微任务队列
得有【宏任务队列和微任务队列】这些概念才能更好理解代码
假设浏览器底层有以下代码
var macroTask = [];//宏任务
var microTask = [];//微任务
var timer;//事件轮训的定时器
window.setTimeoutx = function (cb) {
macroTask.push(cb);
}
//模拟Promise.resolve(1).then
window.micpush = function (cb) {
microTask.push(cb);
}
var eventLoop = function () {
clearTimeout(timer);
//这块代码是为了演示作用,正常环境不会有==========【
if (macroTask.length || microTask.length) {
console.warn("macroTask", macroTask, 'microTask', microTask)
}
else {
console.warn("宏任务和微任务队列为空")
}
//这块代码是为了演示作用,正常环境不会有==========】
function callListFn(arr, ismac) {
let curr = arr.shift();
curr();
//再检测下是否有微任务
//这里其实这样写不啊合适,只是暂时配合模拟效果
if (ismac) {
//微任务
while (microTask.length) {
callListFn(microTask);
}
}
}
//微任务
while (microTask.length) {
callListFn(microTask);
}
//宏任务
while (macroTask.length) {
callListFn(macroTask, true);
}
//模拟事件循环,定时轮询
timer = setTimeout(() => {
eventLoop();
}, 1000)
};//事件循环
//开启事件循环
eventLoop();
我们开发时候所写的代码
- setTimeoutx模拟window.setTimeout
- micpush 模拟Promise.resolve(1).then 这个api
setTimeoutx(()=>{
console.log("timer1")
//模拟Promise.resolve(1).then 这个api
micpush(()=>{
console.log("promise1")
})
console.log("timer 1normal")
})
setTimeoutx(()=>{
console.log("timer2")
})
console.log("primary")
console.warn("macroTask",macroTask,'microTask',microTask)
你会发现浏览器console.log打印的内容和我们第一段代码一模一样
本质
setTimeout,Promise.resolve(1).then他们都是往底层的任务队列里面添加个函数元素而已,并非任务本身,函数类型的参数才是要被执行的任务,只是执行的权限交给了事件轮询。而事件轮询是定期的遍历执行微任务队列和宏任务队列里面的元素。只是微任务的执行优先于宏任务。
步骤解说
注意序号❕
//1
setTimeout(()=>{
//4
console.log("timer1")
// 5
Promise.resolve(1).then(()=>{
//8 console.log("promise1")
})
//6
Promise.resolve(1).then(()=>{
//9 console.log("promise1")
})
//7
console.log("timer 1normal")
})
//2.
setTimeout(()=>{
//10 console.log("timer2")
})
// 3
console.log("primary")
针对上面不同步骤的微任务队列和 任务队列
//1
macroTask = [fn1];//宏任务
microTask = [];//微任务
//2
macroTask = [fn1,fn2];//宏任务
microTask = [];//微任务
//3
macroTask = [fn1,fn2];//宏任务
microTask = [];//微任务
执行【打印"primary”】
【开始事件轮询1】
//4
执行【打印"timer1”】
macroTask = [fn2];//宏任务,正在执行fn1宏任务,所以要把他移除处队列
microTask = [];//微任务
//5
执行【打印"timer1”】—已经执行了,
macroTask = [fn2];//宏任务,正在执行fn1宏任务,所以要把他移除处队列
microTask = [fn3];//微任务,Promise.resolve(1).then 给微任务队列塞入个元素
//6
执行【打印"timer1”】—已经执行了,
macroTask = [fn2];//宏任务,正在执行fn1宏任务,所以要把他移除处队列
microTask = [fn3,fn4];//微任务,Promise.resolve(1).then 给微任务队列塞入个元素
//7
执行【打印"timer 1normal”】
macroTask = [fn2];//宏任务,没有执行下一个事件轮训所以【本轮事件轮训】宏任务还有一个元素
microTask = [fn3,fn4];//微任务,因为还没有执行下一个事件轮训所以【本轮事件轮训】微任务还有2个元素
//开启第二轮事件轮询,8,9,10,微任务【8,9】优先
最终
microTask = [];//微任务
macroTask = [];//宏任务
以上的代码可以在线测试
疑问?
上面【浏览器底层代码】它不会卡住js的运行么,如果是个死循环的话
答案:
不会,浏览器是多线程的,可以开一个独立的线程去执行这个死循环,和我们的网页渲染,js执行的线程是完全分开的。