【JS】玩转事件循环
1. 前言
这篇文章是想跟大家一起讨论一下javascript
中高大上的Event Loop事件循环机制
。
代码得仔细分析,才能绕的过来,否则容易绕晕,谨慎再谨慎~~~
事件循环主要讲的是异步执行问题,没办法,同步就是顺序执行,没什么好说的。
<font color=gray>至于什么线程、同步异步、调用栈、浏览器线程之类的概念,本文一概没有,有意者可自行查阅。</font>
2. 简介
2.1 任务队列
- 宏任务队列:script整体代码、setTimeout/setInterval……
- 微任务队列:Promise
任务队列在事件循环机制中发挥着核心作用。是我们了解事件循环机制的核心要素。
2.2 执行顺序
1. 无 async/await 的执行顺序
注意点前置:
Promise中的函数属于同步函数
new Promise(function a(resolve, reject) {
console.log('此为同步函数')
})
- setTimeout、setInterval、 Promise.then等函数,不为完成状态的情况下,不会推送到任务队列中
图解:
流程图执行说明:
- 首先执行同步代码
- 执行结束后查看微任务队列是否有任务,有则执行
- 微任务队列清空,查看宏任务队列是否有任务,有则执行
- 循环 2 和 3 。直至两个队列都清空
- 执行结束
栗子:
console.log('1');setTimeout(function() {
console.log('2');
}, 0)
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4');
});
console.log('5')
分析:
- 首先执行同步代码,上述代码中
console.log(1)、console.log(3)
和console.log(5)
都属于同步代码。优先输出。同时执行延时函数、then函数,then函数立即返回结果,推送到微任务队列中 - 检查微任务队列,有
console.log(4)
任务,执行并输出。微任务队列结束。 - 延时函数倒计时完成推入宏任务队列。
- 检查宏任务队列,有
console.log(2)
任务,执行并输出。宏任务队列结束。 - 代码执行结束。
- 输出顺序:
1,3,5,4,2
2.async和await后的时间循环机制
<font color=red>提醒: 部分文章会介绍 await 让出执行权的问题,这里先不讨论,容易绕晕。有兴趣的可以查看以下。</font>
await代码的执行:
async标记的函数属于同步函数
async function log () {
console.log('同步函数')
}
await函数中返回一个promise,则执行后为
pending
状态,将下方的代码作为微任务处理,并且,只有await函数执行之后才会执行下方代码(请看栗子4)async function async2() {
return new Promise(function(resolve) {
console.log('3'); // 3
resolve();
}).then(function() {
console.log('4'); // 6
});
}
console.log(async2()) // Promise { <pending> }
await函数中执行一个promise但不return 或者 resolve 被 setTimeout/setInterval 包裹,则执行后为 结束 状态,将下方的代码作为同步代码执行
// await函数中执行一个promise但不return
async function async2() {
// 区别在于没有return
new Promise(function(resolve) {
console.log('3'); // 3
resolve();
}).then(function() {
console.log('4'); // 6
});
}
console.log(async2()) // Promise { undefined }
// resolve 被 setTimeout/setInterval 包裹
async function async2() {
new Promise(function(resolve) {
console.log('3');
setTimeout(() => {
resolve();
}, 0)
}).then(function() {
console.log('4');
});
}
console.log(async2()) // Promise { undefined }
栗子1:
async function async1() {console.log('1');
await async2();
console.log('2');
}
async function async2() {
return new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4');
});
}
console.log('5');
setTimeout(function() {
console.log('6');
}, 0)
async1();
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8');
});
console.log('9');
解析:
- 先执行同步,
console.log(5)、console.log(1)、console.log(3)、console.log(7)、console.log(9)
,将console.log(4)、console.log(8)
放入微任务队列。将console.log(6)
放入宏任务队列。 - 执行
console.log(4)
由于async2
返回的是promise
所以将后面的代码放入微任务队列中。此时微任务队列有两个任务console.log(8)、 console.log(2)
。清空微任务队列,输出8 和 2
。 - 执行宏任务队列。输出
6
- 输出顺序
5,1,3,7,9,4,8,2,6
栗子2(栗子1的变种):
// 将async2改为下面这种写法,其他代码不变async function async2() {
new Promise(function(resolve) {
console.log('3');
resolve();
}).then(function() {
console.log('4');
});
}
解析:
栗子1的第一步保持不变
- 改变第二步,执行
console.log(4)
由于async2
返回的不是promise
所以直接执行后面的代码。输出2
此时微任务队列只有console.log(8)
清空微任务队列,输出8
。 栗子1的第三步保持不变
- 输出顺序:
5,1,3,7,9,4,2,8,6
栗子3(依旧是栗子1的变种,最后一个小栗子):
async function async1() {console.log('1');
await async2();
console.log('2');
}
// 将async2中的resolve函数使用setTimeout包裹 且 return一个Promise
async function async2() {
return new Promise(function(resolve) {
console.log('3');
setTimeout(() => {
resolve();
}, 0)
}).then(function() {
console.log('4');
});
}
console.log('5');
setTimeout(function() {
console.log('6');
}, 0)
async1();
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8');
});
解析:
- 先执行同步,
console.log(5)、console.log(1)、console.log(3)、console.log(7)、console.log(9)
,将console.log(8)
放入微任务队列。将console.log(6)和 console.log(4)
放入宏任务队列。 - 执行微任务
console.log(8)
,输出8
- 执行宏任务
console.log(6)
,输出6
- 由于
async2
返回一个Promise
,所以只能等到async2
执行之后才会将console.log(2)
推入到微任务队列。 - 执行宏任务
console.log(4)
,输出4
并将console.log(2)
推入到微任务中。 - 执行微任务
console.log(2)
,输出2
- 输出顺序:
5,1,3,7,9,8,6,4,2
栗子4(真的是最后一个小栗子了):
// 将栗子3的async2函数改为如下写法async function async2() {
new Promise(function(resolve) {
console.log('3');
setTimeout(() => {
resolve();
}, 0)
}).then(function() {
console.log('4');
});
}
解析:
- 栗子3第一步不变
- 由于
async2
不返回Promise
,所以会将console.log(2)
作为同步代码执行,第二步执行同步任务console.log(2)
输出2
- 执行微任务
console.log(8)
,输出8
- 执行宏任务
console.log(6)
,输出6
- 执行宏任务
console.log(4)
,输出4
- 输出顺序:
5,1,3,7,9,2,8,6,4
无论你是否认真的查看了上面的文章,首先恭喜你可以看到这里,事件队列一直是比较难以理解的javascript
知识点。还是衷心的希望这篇文章能带给你不一样的理解。也祝福正在看文章的你技术越来越好。
以上是 【JS】玩转事件循环 的全部内容, 来源链接: utcz.com/a/99683.html