本文对EventLoop事件循环机制,包括微任务和宏任务,彻底详细的做一个解析,觉得对您有帮助,记得 收藏+关注,欢迎评论区留言~
首先我们要知道浏览器是单线程的还是多线程的,答案是多线程的,具体包括:
答案:JS是“单线程 ”运行的,所以其中大部分代码都是“同步”的(例如:循环…),拿我们举个例子说明JS是不是单线程的:
我们先测一下下面这个循环大概需要多长时间:以及下面代码输出结果
console.time('AA');
for (let i = 0; i < 99999999;i++) { }
console.timeEnd('AA');
console.log('OK');
上述循环需要时间是70ms,根据浏览器性能,需要时间不尽相同,也就是说70ms后输出OK,假如我们去掉i++,那下面代码输出的结果又是怎样的呢?
console.time('AA');
for (let i = 0; i < 99999999;) { }
console.timeEnd('AA');
console.log('OK');
控制台执行过后,我们发现,这个OK永远不会输出,程序进入了死循环,所以,我们发现循环是同步的,即:js是单线程运行的,所以在JS中千万不要写“死循环”、“死递归”等操作,这些操作会一直占用JS引擎线程,导致后续其他的程序都无法执行!但是,JS中也有部分“异步”操作的代码:
异步微任务:
异步宏任务:
JS中的异步操作是:借用浏览器的多线程机制,再基于EventLoop事件循环机制,实现的单线程异步效果!!
答案:setTimeout(() => { }, 0);不是立即执行定时器的,而是要等待5-7ms,这个5-7ms是浏览器最快处理时间。
上个练习题,说说运行的结果:
setTimeout(() => {
console.log(1);
}, 20);
console.log(2);
setTimeout(() => {
console.log(3);
}, 10);
console.log(4);
for (let i = 0; i < 90000000; i++) {
// do soming 80ms左右
}
console.log(5);
setTimeout(() => {
console.log(6);
}, 8);
console.log(7);
setTimeout(() => {
console.log(8);
}, 15);
console.log(9);
答案: 2 4 5 7 9 3 1 6 8 你算对了吗?没有的话请看下面的详细解析:
首先我们要知道 ## EventLoop事件循环机制,
setTimeout(() => {
console.log(1);
}, 20);
因为定时器是异步宏任务,我们把它叫做宏1,我们把它叫 异步宏任务1 ,放到WepAPI任务监听队列中去等待,然后遇到了同步的
console.log(2);
立即输出2,然后再往下执行,遇到了定时器
setTimeout(() => {
console.log(3);
}, 10);
因为定时器是异步宏任务,我们把它叫做宏2,浏览器会把这个定时器放到WepAPI任务监听队列中去等待,,然后遇到了同步的
console.log(4);
,立即输出4,然后代码继续往下执行,遇到了
for (let i = 0; i < 90000000; i++) {
// do soming 70ms左右
}s
这个for循环需要70ms左右,当到这里的时候,当过了10ms的时候,宏2已经到时间了,就放到异步宏任务里面去排队等待执行,然后再过了10ms后,for循环还没结束,宏1已经到时间了,宏1再去‘异步宏任务’队列中去排队等待执行,在等上面我们详细讲了for循环是同步的,所以等for循环循环完成70ms后,代码继续往下执行,遇到了同步的
console.log(5);
立即输出5,然后代码继续往下执行,遇到了定时器,
setTimeout(() => {
console.log(6);
}, 8);
因为定时器是异步宏任务,我们把它叫做宏3,所以放到WebAPI任务监听队列中去等待,然后代码继续往下执行,遇到了同步的
console.log(7);
立即输出7
然后代码继续往下执行,又遇到了定时器
setTimeout(() => {
console.log(8);
}, 15);
因为定时器是异步宏任务,我们把它叫做宏4,,所以放到WebAPI任务监听队列中去等待,然后代码继续往下执行,,遇到了同步的
console.log(9);
立即输出9
所以,第一轮同步代码都执行完了 分别输出 2 4 5 7 9
下面我们看异步代码:
当代码运行到这里的时候,已经过了70ms 这时宏1 和宏2 已经到时间了,并且再“异步宏任务”列表里面排队等待了,宏2 比宏1 先到时间,所以在输出3 和1
然后再去“异步宏任务”队列中去找,发现还有宏3 和宏4 ,宏3 是8ms到时间,所以宏3先执行输出6,然后在等7ms,宏4也到时间了,然后宏4输出9
上述就是这个案例代码运行的详细解析,下图是上述代码宏观描述:不懂请下面放留言,谢谢!
对于EventLoop事件循环机制还有一种情况,我们再来个案例:
let p = new Promise(resolve => {
resolve(10);
});
p.then(value => {
console.log('成功:', value);
});
console.log(1);
分析:结果输出1 ,成功:10,你算对了吗?没有看解析:p.then(onfulfilled,onrejected)的时候,已知实例p的状态和值,也不会立即把 onfulfilled/onrejected 执行,而是创建“异步微任务”「具体怎么做?先进入WebAPI中去排队等着,发现状态是成功,则onfulfilled可以被执行,把其再挪至到EventQueue中排队等着」
另外对于EventLoop事件循环机制我们再看一种情况,我们再来个案例:
let p = new Promise(resolve => {
setTimeout(() => {
resolve(10);
console.log(p, 2);
}, 1000);
});
p.then(value => {
console.log('成功:', value);
});
console.log(1);
分析:结果输出1 ,Promise实例 和2 , 成功:10 ,你算对了吗?没有看解析: 如果还不知道实例p的状态,则先把onfulfilled/onrejected存储起来「理解为:进入WebAPI去监听,只有知道实例的状态,才可以执行」;resolve/reject执行,立即修改实例的状态和值,也决定了WebAPI中监听的方法(onfulfilled/onrejected)哪一个去执行「挪至到EventQueue中,异步微任务队列」;等待其它同步代码执行完,再拿出来执行!!
上述都了解 ,那么我们再来一个有关async和await的来看看:
const fn = async () => {
console.log(1);
return 10;
};
(async function () {
let result = await fn();
console.log(2, result);
})();
console.log(3);
分析:输出结果 1 ,3 ,2 10,你算对了吗?没有看解析:遇到await,会立即执行其后面代码,看返回的promise实例是否是成功,如果不是promise实例也会变为promise实例;不论是成功还是失败,会把当前上下文中,await下面代码当做异步的微任务进入到WebAPI中去监听,只有后面实例的状态是成功的,才可以执行,可执行则进入到EventQueue中排队等着,当同步代码都执行完之后,再把它拿出来执行
你到底有没有彻底了解EventLoop事件循环机制,我们看一个测试题吧(字节的一个面试题):说出下面程序执行结果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
答案是
script start ,async1 start , async2,promise1,script end,async1 end,promise2,setTimeout,你算对了吗?没有看详细解析:首先代码从上往下执行,遇到同步代码
console.log('script start');
立即输出script start,然后继续往下执行,遇到定时器
setTimeout(function() {
console.log('setTimeout');
}, 0)
,因为定时器是异步宏任务,我们把这个定时器叫做宏1,上面说了,宏任务的执行必须等到所以同步代码都执行完成,并且没有“异步微任务”等待的情况下,才能执行,很显然,同步代码还没有执行完成,所以,我把宏1放到WebAPI任务监听队列中去等待,然后,代码继续往下执行,遇到了
async1();
我们上面说了,代码执行遇到async 其后面的代码立即执行,所以,输出‘async1 start’,然后代码继续往下执行,遇到await,会立即执行其后面代码,看返回的promise实例是否是成功,如果不是promise实例也会变为promise实例;不论是成功还是失败,会把当前上下文中,await下面代码当做异步的微任务进入到WebAPI中去监听,只有后面实例的状态是成功的,才可以执行,可执行则进入到EventQueue中排队等着,当同步代码都执行完之后,再把它拿出来执行所以,这里遇到了await,在当前上下文中,不管成功还是失败,await下面代码console.log(‘async1 end’)当作异步微任务(我们叫微1)进入到WebAPI中去监听,然后会立即把async2()函数立即执行,输出‘async2’,返回状态是成功,值是undefined的实例,,函数执行返回状态是成功,值是undefined,那么‘微1’就可以执行了,然后代码继续往下执行,接下来把‘微1’挪到‘异步微任务’队列中排队等着,然后代码继续往下走,遇到了
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
上面我们说了,代码执行遇到 new Promise会立即执行,根据执行结果是成功还是失败来决定.then()中onfulfilled/onrejected哪个执行,那么就先把.then()当作异步微任务(我们叫‘微2’)放到WebAPI任务监听队列中去排队,很显然new Promise执行先输出‘promise1’,返回状态是成功,值是undefined的实例,‘微2’就可以执行了,挪到‘异步微任务’队列中放到‘微1’后面去排队等,然后代码继续往下执行,遇到了同步的
console.log('script end');
立即输出 ‘script end’,到这里,同步代码都执行完了,下面我们看异步代码
首先我们去‘异步微任务’中去找,有‘微1 和 微2’,谁下来的先执行谁,微1执行输出 ‘async1 end ’,然后‘微2’执行输出 promise2,当所有的异步微任务都执行完,异步用任务才执行,这是‘宏1’时间到了,别挪到了‘异步宏任务里面排队等着’,然后输出setTimeout,下面是用画图具体说明:有不明白的下方评论区留言
下面我们再送个阿里的没面试题,说出下面程序执行结果,看看你学会了?
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
答案:2 1 4 3 如果你没做对,看下面的详细解析吧;
首先进来有两个队列 WebAPI队列和EventQueue队列,EventQueue队列中又分为‘异步微任务’和‘异步宏任务’,接下来代码执行,首先获取body,然后点击body,上面我们说 事件绑定属于异步宏任务,我们把
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
叫做‘宏1’,然后又做了个事件绑定
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
我们叫‘宏1’,把‘宏1’和‘宏2’放到WebAPI中监听,当点击body的时候才出发执行,所以body只要不点击,‘宏1’和‘宏2都不执行,假设我们点击 了body ,‘宏1’和‘宏2’都可以执行了,‘宏1’先做的事件绑定,所以‘宏1’先执行,‘宏1’执行过程当中遇到.then()想都不要想直接把.then后面的创建异步微任务,我们叫‘微1’,放到WebAPI中监听,‘微1’获取的实例状态是成功的 也不会立即执行,需要挪到‘异步微任务’队列中去排队,代码继续往下走,遇到
console.log(2);
立即输出2,所以 ‘宏1’执行,创建微1,并且输出2
宏1执行完之后,再往下找,这是有个问题是已经有了微1 和宏2,上面说了 异步微任务的优先级高于异步宏任务的优先级,所以微1 比宏2 先执行,微1 执行输出1,然后宏2 再执行,遇到.then,那么.then后面的被创建异步微任务,我们叫微2,Promise().resolve()执行返回状态是成功,值是undefied的实例,那么代码继续往下执行遇到同步的
console.log(3);
立即输出3,然后还剩一个微2,微2执行输出3,下面是画图宏观说明:
小伙伴们,你有没有彻底掌握EventLoop事件循环机制 以及微任务和宏任务,欢迎下面评论区留言,我看到会第一时间给您回复,如果觉得这个文章对您有帮助,收藏+关注吧,面试之前打开看看