小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文是学习前端小智的学习笔记整理,还有以及宏任务微任务的一些理解。juejin.im/post/5d8020…
🚀 JS 是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说JS引擎一次只能在一个线程里处理一条语句。 这个想必大家都知道。
单线程简化了编程代码,但是也意味着在阻塞主线程的情况下执行长时间的操作,如网络请求。从API请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。由此引入了异步。
1.调用栈:
调用栈这个东西,个人理解就跟盖房子砌墙的感觉一样,最开始盖上去的砖头🧱都压在下面了,个人是这么理解last in first out的,也就常说的LIFO;下面来看下下面代码:
const two = () => {
console.log('我是第二个完事的');
}
const one = () => {
console.log('我第一个完事儿');
two();
console.log('我是最后一个😱');
}
one();
- 当执行此代码时,将创建一个
全局执行上下文(由main()表示)并将其推到调用堆栈的顶部。当遇到对one()的调用时,它会被推送到堆栈的top顶部。 - 接下来,
console.log('我第一个完事儿')被推送到堆栈的顶部;当它完成时,它会从堆栈中弹出。之后,我们调用two(),因此two()函数被推到堆栈的顶部。 two函数中console.log('我是第二个完事的')被推送到堆栈顶部,并在完成时弹出堆栈。two()函数也结束,因此它从堆栈中弹出。console.log('我是最后一个😱');被推到堆栈的顶部,并在完成时删除。最后,one()函数完成,因此从堆栈中删除它。- 程序在这一点上完成了它的执行,所以全局执行上下文(
main())从堆栈中弹出。
2.异步代码是如何执行的:
来看下下面的代码:
const mockReq = () => {
setTimeout(() => {
console.log('憨皮,我是异步中的代码code');
}, 2000);
};
console.log('你好呀,GAMEBOY~!');
mockReq ();
console.log('the end');
- 当上述代码在浏览器中加载时,
console.log(' 你好呀,GAMEBOY~!')被推送到堆栈中,并在完成后弹出堆栈。接下来,将遇到对mockReq()的调用,因此将它推到堆栈的顶部。 mockReq()中的setTimeout()函数被调用,因此它被推到堆栈的顶部。setTimeout()有两个参数:- 回调函数
- 以毫秒为单位的时间
setTimeout()方法在web api环境中启动一个2s的计时器。此时,setTimeout()已经完成,并从堆栈中弹出。console.log(“the end”)被推送到堆栈中,在完成后执行并从堆栈中删除。- 同时,计时器已经过期,现在定时器里面的回调函数被推送到消息队列。但是它不会立即执行,这就是
事件轮询开始的地方。
3.事件轮询:
事件轮询的工作是监听调用堆栈,并确定调用堆栈是否为空。
-
调用堆栈:
调用堆栈不为空:就执行里面的内容调用堆栈是空的:它将检查消息队列,看看是否有任何挂起的回调等待执行。
在这种情况下,消息队列包含一个回调,此时调用堆栈为空。因此,事件轮询将回调推到堆栈的顶部。
然后是
console.log(“憨皮,我是异步中的代码code”)被推送到堆栈顶部,执行并从堆栈中弹出。此时,回调已经完成,因此从堆栈中删除它,程序最终完成。
4.宏任务和微任务:
浏览器中的事件轮询eventLoop,分为
- 同步执行栈:优先执行同步任务
- 异步消息队列:当同步任务执行完之后会从异步消息队列中取异步任务,拿到
同步执行栈中进行执行
🌵异步消息队列的中的任务可分为下面两类:
宏任务和微任务:
- 宏任务:优先级低,先定义的先执行。包括:
ajax,setTimeout,setInterval,事件绑定,postMessage,MessageChannel(用于消息通讯) - 微任务,
优先级高,并且可以插队,不是先定义先执行。包括:promise 中的 then,observer,MutationObserver,setImmediate
还是通过例子来理解消化吧,只可意会,尽量言传。
🚀GAME1:
setTimeout(function() {
console.log(4);
}, 0);
new Promise(function(reslove) {
console.log(1);
reslove();
}).then(function(data) {
console.log(3);
});
console.log(2);
//会输出:1,2,3,4。
🏖GAME1-PROGRESS:
-
先将
setTimeout加入到异步消息队列的宏任务池中 -
然后执行
promise中的console.log(1) -
再将
promise的.then加到异步消息队列中微任务池中 -
再执行同步任务
console.log(2),当同步任务都执行完之后,先取微任务队列中的任务,执行console.log(3) -
微任务执行完之后取宏任务,执行
console.log(4)。所以顺序就是:1,2,3,4。
注意📢:
🌵promise 中是同步任务,promise 的 .then 中是异步任务~!!!
🚀GAME2:
setTimeout(function() {
console.log(4);
}, 0);
new Promise(function(reslove) {
console.log(1);
setTimeout(function() {
reslove("我勒个擦MMP"); //这里需要注意,这里如果是reslove就不会执行的,如果是console.log就会执行
console.log("你的名字叫十二");
}, 0);
reslove('first');
}).then(function(data) {
console.log(data);
});
console.log(2);
//输出结果:
//1,2,first,4,你的名字叫十二
🏖GAME2-PROGRESS:
这个时候就会输出:1,2,first,4,你的名字叫十二,没有输出"我勒个擦MMP"
有些人可能会想,应该输出 1,2,first,4,你的名字叫十二,我勒个擦MMP,这个时候就要知道,当使用 reslove 之后,promise 的状态就从 pending 变成了 resolve。
promise 的状态更改之后就不能再更改了,所以 reslove('我勒个擦MMP') 就不会执行了。
🚀GAME3:
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function(reslove) {
console.log(2);
reslove('p1');
new Promise(function(reslove) {
console.log(3);
setTimeout(function() {
reslove('setTimeout2');
console.log(4);
}, 0);
reslove('p2');
}).then(function(data) {
console.log(data);
});
setTimeout(function() {
reslove('setTimeout1');
console.log(5);
}, 0);
}).then(function(data) {
console.log(data);
});
console.log(6);
//打印结果:
// 2,3,6,p2,p1,1,4,5
🏖GAME3-PROGRESS:
-
先执行同步的任务,
new Promise中的都是同步任务,所以先输出2,3,6 -
然后再执行微任务的,这里面会先执行p2里面的逻辑,然后再走外层逻辑。resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;promise调用then的前提是promise的状态为fullfilled;只有promise调用then的时候,then里面的函数才会被推入微任务中
-
然后执行
p1,当微任务都执行完成之后,执行宏任务,宏任务依次输出1,4,5,promise的状态不可以变更,所以setTimeout1和setTimeout2不会输出。
🌵特别声明:
在node环境下,process.nextTick的优先级高于Promise,也就是说:在宏任务结束后会先执行微任务队列中的nextTickQueue,然后才会执行微任务中的Promise。