详细:https://github.com/febobo/web-interview
5.事件循环
js是一种单线程语言,同一时间内只能做一件事情,为了避免单线程阻塞的方法就是事件循环。
在javascript当中,所有的任务都可以分为:
-
同步任务:按顺序执行的任务,一个任务的执行必须等待前一个任务的完成。同步任务一般会直接进入主线程执行。
-
异步任务:不按顺序执行的任务,可以在后台执行,不会阻塞代码的执行。比如:
ajax
网络请求,setTimeout,setinterval
定时函数等,Promise
,async/await
等。
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环。
①宏任务与微任务
在JavaScirpt当中,宏任务(macrotask)和微任务(microtask)是异步任务的两种类型,它们有不同的执行时机和优先级。
宏任务(macrotask)
-
宏任务代表的是一组任务的执行,可以看作是每一个独立的、完整的任务。
-
常见的宏任务包括整体的代码块(scirpt)、setTimeout、setInterval、I/O操作、UI渲染等。
-
当执行栈中的任务执行完毕后,js运行时会检查是否有宏任务需要执行,然后选择一个宏任务执行,执行完后再次清空执行栈。
微任务(microtask)
-
相对于更“细粒度”的任务,执行时机位于当前任务执行结束后、下一个宏任务开始之前。
-
常见的微任务包括Promise的处理、MutationObserver变动观察器等。
-
微任务通常用于处理异步操作的结构,其优先级高于宏任务,及在当前宏任务执行完毕后,会优先执行微任务队列中的任务,直到微任务队列为空,才会执行下一个宏任务。
在执行顺序上,JavaScript 的事件循环(Event Loop)会先执行当前执行栈中的任务,然后检查是否有微任务需要执行,如果有,会依次执行微任务队列中的所有任务,直到微任务队列为空;最后再执行下一个宏任务。这样的循环过程不断重复,直到所有任务执行完毕。
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
如果按照上面流程图来分析代码,我们会得到下面的执行步骤:
1.console.log(1):首先会打印出 1,因为这是同步代码,会立即执行并输出 1。
2.遇到 setTimeout,会将回调函数推入宏任务队列,等待当前执行栈清空后执行。
3.遇到 Promise 对象,会立即执行 Promise 的构造函数中的代码,输出 new Promise。
(1)紧接着,resolve() 会立即执行,表示 Promise 状态变为 resolved,进入 then 方法的回调链。
(2)由于 Promise 的状态是 resolved,所以 then 方法中的回调函数会被放入微任务队列中等待执行。
(3)接着会打印出 3,因为这是同步代码,会立即执行并输出 3。
4.此时宏任务执行完成,开始执行微任务队列执行 then 方法,输出 then
5. 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,输出 2
结果是:1
=>'new Promise'
=> 3
=> 'then'
=> 2
详情可见Jake的博客:Tasks, microtasks, queues and schedules,这篇文章非常经典。