因为 JavaScript 是单线程语言,当执行过程中遇到了非常耗时的操作时,线程中的下一个操作只能等待这个操作完成后才能执行,这样会造成页面发生卡顿,为了解决这样的问题,就出现了异步编程。
1、js 代码执行时,浏览器会开启一个主线程,用于 js代码的执行,代码从上到下按顺序执行,遇到异步任务,只要异步任务有了运行结果,就会将其回调函数作为一个任务添加到任务队列中。
2、等主线程中的同步任务全部执行完成后,就会去任务队列中查看是否有任务,如果有,就将任务放入到主线程中执行,如果在执行过程中,又遇到了异步任务,则再次将新的回调函数放入到任务队列中,这时主线程又被占用了。
事件循环机制:不断地循环异步编程的操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAGZrzve-1650413872387)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220419151923240.png)]
宏任务 | 浏览器 | Node |
---|---|---|
setTimeOut | √ | √ |
setInterval | √ | √ |
setImmediate | × | √ |
requestAnimationFrame | √ | × |
主要记忆的:setTimeOut,setInterval,整体代码script
微任务 | 浏览器 | Node |
---|---|---|
process.nextTick | × | √ |
MutationObserver | √ | × |
Promise.then catch finally | √ | √ |
主要记忆的:Promise,process.nextTick,
注意:promise传入的执行函数会立即执行属于同步
一般来说,微任务先于宏任务执行,执行宏任务时,遇到微任务,先执行微任务,再执行之后的宏任务
注意:不是在所有情况下,微任务都先于宏任务执行!!! 当微任务的状态不确定时,它不会被加到任务队列中,此时是任务队列中的宏任务先执行,只有等微任务的状态确定之后,它才会被加入到任务队列中,才会被执行。
事件循环机制过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xofOP7nD-1650413872396)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220419164603353.png)]
来练练手叭~
let p1 = new Promise((resolve,reject)=>{
console.log(1);
setTimeout(()=>{
resolve('ok');
console.log('2');
},1000);
})
p1.then((ret)=>{
console.log('success:',ret);
},(reason)=>{
console.log('failed:',reason);
});
console.log(3);
// 1
// 3
// 2
// success: ok
分析:
首先会按代码顺序执行同步任务,而上文中有说到,promise传入的执行函数属于同步,所以会先执行输出 ’ 1 ’ ;然后遇到了setTimeout,把它加入到任务队列中,然后是还没有确定状态的微任务,先挂起;最后是输出 ‘ 3 ’。
执行完同步任务后,检查任务队列中有没有任务,发现有setTimeout(宏任务)存在,然后执行setTimeout,调用 resolve (‘ok’),可以知道 p1 的状态;这时将 then 的回调(微任务)加入到任务队列中;但是此时因为setTimeout还在主线程中运行,所以先输出 ‘ 2 ’ ;输出 ‘ 2 ’之后说明这次的同步执行完成啦。
接下来要去任务队列中查找任务,这时 then 的回调已经加入到队列中啦,所以要把这个微任务放到主线程中执行,最后输出了 success: ok
再来一题练练叭~(这个例子是我在网上找的,侵删)
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
console.log('script start');
setTimeout(function s1(){
console.log('setTimeOut1');
Promise.resolve().then(() => {
console.log('s1 promise');
});
outer.setAttribute('data1-random', Math.random());
}, 0)
new MutationObserver(function () {
console.log('mutate');
}).observe(outer, {
attributes: true,
});
new Promise(function p1(resolve){
console.log('p1 start');
setTimeout(function s2() {
console.log('setTimeOut2');
resolve();
console.log('p1 end');
},0)
}).then(function(){
console.log('promise2');
setTimeout(function s3(){
outer.setAttribute('data3-random', Math.random());
console.log('setTimeOut3');
}, 0)
})
console.log('script end');
//script start
//p1 start
//script end
//setTimeOut1
//s1 promise
//mutate
//setTimeOut2
//p1 end
//promise2
//setTimeOut3
//mutate
分析:
首先还是先执行主线程中的同步任务,输出 ‘ script start ’ ,
setTimeout( s1)是宏任务,加入到任务队列中,
MutationObserver是微任务,但是它需要触发回调才能执行,所以先挂起;
然后到了promise传入的执行函数 p1; 输出 ‘p1 start’ ,
setTimeout(s2) 是宏任务,加入到任务队列中
因为Promise p1 还没有确认状态,所以promise(p1).then 先挂起
继续执行同步任务, 输出 ‘script end’;
主线程中同步任务执行完成后,开始查看任务队列中是否有任务,首先是 setTimeout( s1),
将setTimeout( s1)放入到主线程中,开始执行,首先输出 ‘setTimeOut1’;
触发 Promise.resolve().then(s1)回调,将 Promise.resolve().then(s1)回调函数(微任务)加入到任务队列中,因为它是微任务,所以先执行,输出 ‘ s1 promise ’;
outer.setAttribute(‘data1-random’, Math.random()触发MutationObserver() 回调函数,因为它是微任务,所以执行MutationObserver() 回调函数,输出 ‘mutate ’;
主线程执行完成后,检查任务队列,将 setTimeout( s2)放入到主线程中,开始执行,输出 ‘setTimeOut2’
resolve( ) 触发 Promise.resolve().then(s2)回调,将回调函数(微任务)加入到任务队列中,
然后继续执行主线程中同步任务,输出 ‘p1 end’
接着执行 Promise.resolve().then(s2)回调,输出 ‘promise2’ ;
setTimeout(s3)是宏任务,加入到任务队列中
主线程同步任务执行完成,检查队列中是否还有任务,将setTimeout(s3)放入到主线程中执行
outer.setAttribute(‘data1-random’, Math.random()触发MutationObserver() 回调函数,因为它是微任务,先加到任务队列中,等主线程同步任务执行完成后,才能执行,
所以输出 ‘setTimeOut3’
最后从任务队列中,将微任务放到主线程中,执行MutationObserver() 回调函数,输出 ‘mutate ’;
参考文章:
重学JS|这次聊聊EventLoop
彻底搞懂JS事件循环机制(event loop)
理解JS异步编程(二)、async、await