为什么有事件循环?
因为JS是一门单线程的语言,它运行在浏览器渲染主线程中,而渲染主线程只有一个。
渲染主线程承担着许多的工作,渲染页面和运行JS都在其中执行。
如果是同步的方式,就很有可能发生线程阻塞,如果有一个耗时的任务,就会导致后面的任务全部阻塞。
导致页面无法实时更新,给用户造成页面的卡顿的现象。
所以浏览器需要采用异步的方式,去解决线程阻塞。
具体的做法是,当某些任务执行时,比如: 定时器, 网络, 事件监听等任务执行, 主线程会交给其他线程去处理, 自己去执行后续的代码。
当其他线程任务完成时, 就会把任务的回调函数打包成任务, 放到主线程的队列中去, 等待主线程去执行。
事件循环
事件循环,又称为消息循环,在V8源码中是叫messageLoop。
事件循环中有任务和任务队列,任务是没有优先级的,但任务队列有优先级。
过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,在v8源码中, 任务队列类型有很多种,高达几十种。
在目前的chrome的实现中,常用的任务队列有以下三种:
- 微任务队列 (优先级最高) (Promise.then(), MutationObserver)
- 交互任务队列 (优先级高) (队列中存放的是,用户的交互事件)
- 计时任务队列 (优先级中) (setTimeout, setInterval)
根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
练习
接下来,我们根据一块练手代码熟悉一下事件循环:
console.log('1');
Promise.resolve(2).then((res) => {
console.log(res);
Promise.resolve(7).then(res => {
console.log(res);
})
})
Promise.resolve(8).then(res => console.log(res))
console.log('3');
function fn1() {
console.log('4');
fn2()
setTimeout(() => {
console.log('5');
}, 0)
Promise.resolve(9).then(res => console.log(res))
}
fn1()
function fn2() {
console.log('6');
}
setTimeout(() => {
console.log('10');
}, 0)
解析
- 主线程从第一行开始执行, 遇到微任务或者计时任务交给相应的队列
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 ---
-
计时队列 ---
-
交互队列 ---
- 遇到第3行时,把这个任务放入到微任务队列中, 为了方便展示, 我用输出值代表这个任务
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 --- 2
-
计时队列 ---
-
交互队列 ---
- 遇到第10行时, 放入队列
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 --- 2 8
-
计时队列 ---
-
交互队列 ---
- 遇到第17行时, 是一个计时器, 放入计时队列中去
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 --- 2 8
-
计时队列 --- 5
-
交互队列 ---
- 遇到20行时, 微任务放到微任务队列中
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 --- 2 8 9
-
计时队列 --- 5
-
交互队列 ---
- 遇到第29行时, 放入计时队列中去
log : 1 3 4 6
-
主线程 --- 全局jS
-
微任务队列 --- 2 8 9
-
计时队列 --- 5 10
-
交互队列 ---
- 开始根据优先级,执行队列中的任务
log : 1 3 4 6
-
主线程 --- 2
-
微任务队列 --- 8 9
-
- 计时队列 --- 5 10
-
交互队列 ---
- 看第3行,当执行3行的时候,输出2,并执行另一个微任务,需要继续放入微任务队列中
log : 1 3 4 6 2
-
主线程 ---
-
微任务队列 --- 8 9 7
-
计时队列 --- 5 10
-
交互队列 ---
9.继续执行队列中的任务, 第10行就是普通打印, 正常输出即可
log : 1 3 4 6 2 8
-
主线程 --- 8
-
微任务队列 --- 9 7
-
计时队列 --- 5 10
-
交互队列 ---
10.继续执行队列中的任务, 第20行就是普通打印, 正常输出即可
log : 1 3 4 6 2 8 9
-
主线程 --- 9
-
微任务队列 --- 7
-
计时队列 --- 5 10
-
交互队列 ---
11.继续执行队列中的任务, 第6行就是普通打印, 正常输出即可
log : 1 3 4 6 2 8 9 7
-
主线程 --- 7
-
微任务队列 --- 7
-
计时队列 --- 5 10
-
交互队列 ---
11.微任务执行完毕, 开始执行计时队列中的任务
log : 1 3 4 6 2 8 9 7 5
-
主线程 --- 5
-
微任务队列 ---
-
计时队列 --- 10
-
交互队列 ---
11.微任务执行完毕, 开始执行计时队列中的任务
log : 1 3 4 6 2 8 9 7 5 10
-
主线程 --- 10
-
微任务队列 ---
-
计时队列 ---
-
交互队列 ---
12.任务全部执行完毕
打印 1 3 4 6 2 8 9 7 5 10
以上知识来自渡一教育袁老师的大师课, 我也是跟他学习的