在进入事件循环机制前,先回顾下基础知识
- 函数调用栈
- 执行上下文
javascript代码在执行时,会进入一个执行上下文。执行上下文可以理解 为当前代码的运行环境。
javscript中的主要三种运行环境
- 全局环境(代码运行起来后会首先进入全局环境)
- 函数环境(当函数被调用执行时,会进入当前函数中执行代码)
- eval环境(不建议使用 不详述)
可以预见的是,在一个JS程序中,必定会出现多个执行上下文。Javascript引擎会以栈的方式处理运行顺序,这个栈即函数调用栈。栈顶是当前执行的上下文,栈底是全局上下文。执行上下文的运行顺序是后进先出,执行完的就会弹出
大多数代码可以通过函数调用栈规则执行,但是遇到setTimeOut/SetInterval或者不同的事件绑定时,则通过队列执行。
任务队列分为宏任务(macro-task)和微任务(micro-task),在浏览器中,包括
- macro-task:script(整体代码),setTimeOut/SetInterval,I/O,UI rendering
- micro-task:Promise
执行顺序是
- 第一次循环时,macro-task中其实只要script,因此函数调用栈清空后,会直接执行所有的micro-task。当所有micro-task执行完毕后,就表示第一次事件循环结束。
- 第二次循环会再次从macro-task开始。此时script已经没有内容了,但是可能会有其他队列任务,而micro-task是清空的。此时会先选择最早放入的一个宏任务队列执行,例如setTimeOut,然后在执行过程中可能会产生新的微任务。微任务执行完毕后,再回过头来执行其他宏任务队列中的任务。依次类推到所有宏任务队列清空并且微任务也清空则第二次循环结束。
- 如果在第二次循环过程中,产生了新的宏任务队列,或者之前宏任务队列中的任务暂时没有满足执行条件,例如延迟时间不够或者时间没有触发,那么将会继续重复循环。
一道题目考察
setTimeout(function () {
console.log('timeout1')
})
new Promise(function (resolve) {
console.log('promise1')
for(var i=0;i<1000;i++){
i==99&resolve()
}
console.log('promise2')
}).then(function () {
console.log('then1')
})
console.log('global1')
输出结果:promise1,promise2,global1,then1,timeout1