写在前面的:
才疏学浅,如有不足,多多指出!
之前在关于移动端的长按事件文章中曾经提过 setTimeout
,搜索该函数的时候有一道题目让我泛起了很大的疑惑,似乎自己对这方面知识一直不太懂。
console.log("global");
// S_111
setTimeout(function() {
console.log("timeout1");
// P_111
new Promise(function(resolve) {
console.log("timeout1_promise");
resolve();
}).then(function() {
console.log("timeout1_then");
});
}, 2000);
for (var i = 1; i <= 5; i++) {
// S_222
setTimeout(function() {
console.log(i);
}, i * 1000);
console.log(i);
}
// P_222
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("then1");
});
// S_333
setTimeout(function() {
console.log("timeout2");
// P_333
new Promise(function(resolve) {
console.log("timeout2_promise");
resolve();
}).then(function() {
console.log("timeout2_then");
});
}, 1000);
// P_444
new Promise(function(resolve) {
console.log("promise2");
resolve();
}).then(function() {
console.log("then2");
});
👇
👇
👇
想要知道答案,可以直接拖到文章最后查看。
如果想要知道上述代码运行的结果,则需要理解以下几个知识点:
1. JS单线程、异步、同步概念
- JS单线程:只有一个主线程,串行执行一个个任务,后一个任务必须等待前一个任务的执行完成。
- 异步:由于类似
ajax网络请求
、setTimeout时间延迟
、DOM事件的用户交互
等,需要一些时间的等待,却不需要CPU处理,因此异步执行就是跳过了CPU等待,先去执行主线程中后续任务,等异步模块处理完后在执行相应任务。 - 同步:同步执行是主线程按照顺序,串行执行任务。
由此产生了任务队列
与事件循环
,来协调主线程与异步模块之间的工作。
2.事件循环机制
-
执行顺序
step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件、Timer到等),将相应的异步任务推入任务队列;
step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);
step5: 重复执行step2、3、4;称为事件循环。 -
执行大意:
同步环境执行(step1) -> 事件循环1(step4) -> 事件循环2(step4的重复)… -
异步进程:
a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
3.任务队列
- 类型:
宏观任务队列(macrotask queue
):如setTimeout、setInterval
微观任务队列(microtask queue
):如ES6 的Promise.then() - 顺序
在某一次事件循环中,先执行微任务,按串行执行完所有的微任务以后;再去执行宏任务。
4.分析
1)首行直接输出global
;
2)遇到第一个 setTimeout,简称 s_111,是宏任务,新建宏任务队列并将其插入队列后,设定为 2 秒后执行。
3)for 循环,遇到 s_222,插入刚才的宏任务队列,输出 i;循环结束后,此时应该在宏任务队列中插入了5个任务,分别延时1-5秒执行;并且直接输出 1,2,3,4,5
4)p_222,Promise 构造函数在实例化时直接执行,因此输出promise1
,发现P_222 的 then为微任务,新建微任务队列,将其添加进入;
5) s_333:加入宏任务队列中,1秒后执行。
6)p_444: 同理,直接输出promise2
,将P_444 的 then加入刚才的微任务队列;
7)此时,同步环境执行完了可以直接输出的代码,接着执行微任务,也就是P_222 的 then 与 P_444 的 then,此时输出then1
与 then2
第二轮事件循环开始,执行setTimeout类型队列:
按照时间顺序,其次可以知道该队列顺序应该为:
s_222(for1) -> s_333 -> s_111 -> s_222(for2) -> s_222(for3) -> s_222(for4) -> s_222(for5)
时间越短的任务越前,时间相同时,先进队列的任务越前。
8)s_222(for1) :此时 i 已经变成6,所以输出6
;
9) s_333:先输出timeout2
, timeout2_promise
,此时出现了微任务p_333的then,因此会先执行完该任务的所有任务,继续输出timeout2_then
10) s_111:同上分析,输出timeout1
, timeout1_promise
, timeout1_then
11)接着继续执行刚才for循环中的setTimeout,每隔一秒输出6
。
12)第二轮循环结束,全部代码执行完毕。
👇
👇
👇
最终结果展示为

网友评论