目标
理解浏览器中的EventLoop(事件循环)的概念
理解
js是单线程的,一次只能做一件事。js在浏览器这个宿主环境中运行。浏览器是多线程的,用户交互,定时器,网络请求等等浏览器中的事件会产生对应的任务,任务多了要在任务队列中排队,浏览器的主线程依次取出任务来执行,此过程不断重复从而形成一个循环,称为eventLoop。
js代码分类
1.同步代码:
除了异步代码剩余的就是同步代码
2.异步代码(异步任务):
不是马上执行,是放入到队列中等待;如果所有的任务都要按序等待,那么也不行,需要有一个能插队的机制。所以又将异步任务分为微任务和宏任务,同时对应微任务队列和宏任务队列。
微任务队列和宏任务队列:
(1)宏任务:
script标签
事件处理函数
ajax请求
定时器(setTimout)
(2)微任务:
async / await
promise的then
事件循环规则
1.从上往下逐行 "解析"代码
2.判断代码是同步还是异步
3.如果是同步, 则立即执行
4.如果是异步, 则不会立即执行. 微任务就放入微任务队列, 宏任务就会放入宏任务队列
5.等所有同步执行完毕才会执行异步
6.先执行异步队列中的微任务, 再执行宏任务, 完成本次事件循环
7.按照上面的规则重复解析宏任务(事件循环)
案例
1.简单写写:
<script>
console.log(1)
setTimeout(function () {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(1000)
})
p.then(data => {
console.log(data)
})
console.log(3)
</script>
解析:
在代码执行过程中,先执行同步代码,并输出结果;遇到异步代码之后,交由浏览器(宿主环境)处理 ===> 事件触发之后进入到任务队列继续执行后续代码
所以按照从上往下的顺序依次解析:
(1)同步代码直接执行,所以直接输出:1
console.log(1)
(2)定时器属于异步任务中的宏任务,所以放入宏任务队列等同步代码和微任务先执行完,再执行
setTimeout(function () {
console.log(2)
}, 0)
(3)Promise的then属于异步任务中的微任务,所以放入微任务队列等同步代码先执行完,再执行。(p的结果为resolve,执行then,data = 1000)
const p = new Promise((resolve, reject) => {
resolve(1000)
})
p.then(data => {
console.log(data)
})
(4)同步代码直接执行,所以直接输出:3
console.log(3)
(5)代码解析完毕,其中同步代码直接执行,控制台先输出结果为:1、3;按循环顺序同步执代码行完毕后再执行异步代码,异步代码又先执行微任务队列,所以控制台输出结果:1000,最后执行宏任务队列,输出结果:2.
(6)代码执行完毕,控制台输出结果为: 1、3、1000、2
2.难度提升:
<script>
console.log(1);
async function fnOne() {
console.log(2);
await fnTwo();
console.log(3);
}
async function fnTwo() {
console.log(4);
}
fnOne();
setTimeout(() => {
console.log(5);
}, 2000);
let p = new Promise((resolve, reject) => {
console.log(6);
resolve();
console.log(7);
})
setTimeout(() => {
console.log(8)
}, 0)
p.then(() => {
console.log(9);
})
console.log(10);
</script>
<script>
console.log(11);
setTimeout(() => {
console.log(12);
let p = new Promise((resolve) => {
resolve(13);
})
p.then(res => {
console.log(res);
})
console.log(15);
}, 0)
console.log(14);
</script>
解析:
(1)在有多个script标签的情况下,根据规则将script放入宏任务队列,从上往下依次执行
(2)执行第一个script
(3) 同步先执行,输出结果为:1
console.log(1);
(2)fnOne()调用函数,输出结果:2;调用fnTwo()输出结果:4;await关键字: (1)取代then (2)获取then结果,所以这里的console.log(3)等价于.then(res => {console.log(3)}),将其移入到微任务队列等待执行
async function fnOne() {
console.log(2);
await fnTwo();
console.log(3);
}
async function fnTwo() {
console.log(4);
}
fnOne();
(3)定时器移入宏任务队列
(4) new Promise()里的函数体会马上执行所有代码,输出结果:6、7
(5)定时器移入宏任务队列
setTimeout(() => {
console.log(8)
}, 0)
(6)上面p的结果为resolve(),.then可以执行,所以p.then移入微任务队列等待
(7)同步直接执行,输出结果:10
console.log(10);
(8)同步执行完毕,再执行微任务,输出结果:3、9
(9)微任务执行完毕,再执行宏任务(定时器设置了时间,就按照时间先后排列)
(10)先执行第二个script
<script>
console.log(11);
setTimeout(() => {
console.log(12);
let p = new Promise((resolve) => {
resolve(13);
})
p.then(res => {
console.log(res);
})
console.log(15);
}, 0)
console.log(14);
</script>
(11)再次按照规则执行,同步直接执行,输出结果:11
console.log(11);
(12)定时器移入宏任务
setTimeout(() => {
console.log(12);
let p = new Promise((resolve) => {
resolve(13);
})
p.then(res => {
console.log(res);
})
console.log(15);
}, 0)
(13)同步直接执行,输出结果:14
console.log(14);
(14)没有微任务,就直接执行宏任务
(15)从上往下先执行,第一个定时器,输出结果:8
setTimeout(() => {
console.log(8)
}, 0)
(16)执行第二个定时器
(17)同步直接执行,输出结果:12
console.log(12);
(18)p.then属于微任务,移入微任务队列,等待执行
(19)同步直接执行,输出结果:15
console.log(15);
(20)同步执行完毕,先执行微任务,p成功(resolve)执行res = 13,输出结果为:13
(21)微任务执行完毕,再执行宏任务,输出结果为:5
(22)代码执行完毕,控制台输出结果为: 1、2、4、6、7、10、3、9、11、14、8、12、15、13、5
3.练练看
<script>
console.log(1)
new Promise((resolve, reject) => {
resolve(2)
}).then(res => {
console.log(res)
})
setTimeout(() => {
console.log(3)
})
console.log(4)
</script>
<script>
console.log(5)
</script>
<script>
new Promise((resolve,reject)=>{
resolve(6)
}).then(res=>{
console.log(res)
})
setTimeout(()=>{
console.log(7)
})
console.log(8)
</script>
<script>
console.log(9)
setTimeout(()=>{
console.log(10)
})
</script>
可以自己解析一下
代码执行完毕,控制台输出结果为: 1、 4 、2、 5、 8 、6 、9、 3、 7、 10
结语
根据循环规则,一步一步的解析代码,EventLoop事件循环就很容易理解。