js是单线程的非阻塞的语言,它的事件循环机制是它非阻塞的原因。
我们理解其事件循环机制时,要先理解几个概念(本文章讲的是浏览器端的事件循环,非node环境):
执行栈(stack)和任务队列(Task Queue)
当一个脚本第一次执行时,它会将其中的同步代码添加到执行栈里,比如以下代码:
function a() {
function b() {}
}
它就会先将a()压入栈中,再将a()中的b()压入栈中,执行顺序按栈结构的先进后出特性执行,每一次新的执行栈都会产生一个执行环境(执行上下文),执行上下文指向堆里的变量,当执行栈销毁时,对应执行上下文也会销毁。
上面说的是同步代码的运行,那异步代码呢?js是非阻塞的,浏览器不可能等到异步代码执行完再往下走,这时候任务队列(Task Queue)就来了,异步代码都会先放到任务队列里,等同步代码执行完了,在执行任务队列里的异步代码。
微任务(microTask)与宏任务(task)
微任务(microTask)包括:
- Promise.then catch finally
- MutationObserver
宏任务(task)包括
- setTimeout
- setInterval
- I/O(主要是XMLHttpRequest API与fetch)
- requestAnimationFrame
- script( 整体代码)
事件循环(Event Loop)
1.进入script里执行代码
2.依次执行任务里的代码,同步代码压入执行栈执行,异步代码分类放入微任务队列和宏任务队列;同步代码执行完毕后,执行下一步
3.检查微任务队列是否为空,若不为空,则执行所有微任务,完毕后执行下一步
4.检查宏任务队列是否为空,若不为空,依次执行队列里的宏任务,每个任务执行顺序都为(2->3->4),直到宏任务队列为空
5.事件循环完毕
Q.E.D.