一篇文章理解JS中同步任务和异步任务以及宏任务与微任务的原理和执行机制

前言:

javascript是一种单线程编程语言, 一般来说它的执行顺序是按照从上到下执行,但是有些特殊情况则会改变这样的执行顺序,我们需要理解和掌握其中的原理,需要了解同步任务异步任务以及宏任务微任务,这样才有助于我们在今后开发中及时发现代码执行问题,提高开发效率。Javascript 是单线程语言,所以一切 Javascript “多线程” 都是单线程模拟出来的。

1.Javascript 中的同步任务和异步任务

同步任务:可以立即执行不需要等待,例如 var a = 123

异步任务:不能立即执行,需要等待一段时间后才能执行,例如 xhr网络请求 和 setTimeout定时器。

执行流程如下:

  • 执行整个脚本,解析到同步异步任务时分别放进不同的执行场所,同步任务放进主线程,异步任务放进事件表(Event Table)中并注册函数。

  • 当指定的事情执行完成时,事件表(Event Table)会将这个函数移入事件队列(Event Queue)中等待执行。

  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。

  • 不断重复整个过程,形成Event Loop(事件循环)。

举一个例子:

console.log('111') //同步任务 1
setTimeout(()=>{ //异步任务 1
    console.log('222')
},3000)
setTimeout(() => { // 异步任务 2
  console.log('333')
},  1000)
console.log('444') //同步任务 2
答:111 444 333 222

对这个过程进行解析:

  • 同步任务1和2 放入主线程中,将异步任务1和2放入Event Table,并注册setTimeout的回调函数。

  • 执行 主线程中的任务 按照从上到下的先后顺序 先输出 111 后 输出 444

  • 当setTimeout定时结束后返回回调函数进入 Event Queue等待执行。

  • 主线程执行完毕后从Event Queue读取setTimeout的回调函数放入主线程中并执行,因为两个异步任务执setTimeout执行时间不同,所以返回回调函数先后顺序也有所不同。

2.为什么异步任务中要设置队列(事件监听器)?

对于http、xhr等网络请求,执行的时间是不确定的,针对于这种情况设置了消息队列(事件监听器)。

事件监听器可以监听异步任务的状态,如果可以执行回调就会将相应的任务放入事件队列中。

举个例子:

假设两个网络请求,监听器会先监听到第二个请求得到响应,那么会先执行第二个的回调,所以下面这段代码的输出是1 2 3 4

console.log('111') // 同步任务1
ajax().then(() => { // 异步任务1
  console.log('222')
})
ajax().then(() => { // 异步任务2
  console.log('333')
})
console.log('444') // 同步任务2

发起请求的两个异步任务,通过事件监听器来监听,如果异步任务2先返回回调函数,异步任务1后返回回调函数,那么就先执行异步任务1后执行异步任务2,答案则是:111 444 333 222。

3.什么是宏任务和微任务?

如果javascript 按照真正的单线程执行的话,如果在遇到任务繁多的情况下,那么执行效率会降低,执行的时间也会更长,或者在解析到中间代码过程中遇到了错误的代码,那么就会停止在这一步影响后面代码的执行,这是非常不好的用户体验。为了解决这种传统意义上的单线程执行问题,在需要执行很多任务的这种情况下,就需要利用微任务宏任务模拟产生“多线程”才能确保代码更高的执行效率。

宏任务微任务异步任务中的两个分类。

在ES6规范中 宏任务是由宿主发起的,微任务是由Javascript自身发起的。

4.宏任务和微任务区别是什么?

宏任务:由宿主(Node、浏览器等)发起,在微任务后运行,并会触发新一轮的 netxtTick()。

具体事件:

  1. script (可以理解为外层同步代码)

  1. setTimeout(定时器) / setInterval(计数器)

  1. UI rendering / UI事件

  1. postMessage,MessageChannel5. setImmediate,I/O(Node.js)

微任务:由JS引擎发起,在宏任务前运行,不会触发新一轮的 netxtTick()。

具体事件:

  1. Promise

  1. MutaionObserver

  1. Object.observe(已废弃;Proxy 对象替代)

  1. process.nextTick(Node.js)

5.宏任务和微任务执行顺序

执行顺序:

执行脚本,先执行同步代码,遇到异步宏任务则放入到宏任务队列中,遇到异步微任务则放入到微任务队列中,当所有同步代码执行完毕后,再将异步任务从队列中调入主线程执行,先将微任务执行完毕后再将宏任务从队列中调入主线程执行。

异步任务宏任务微任务两种,先将宏任务添加到宏任务队列中,将宏任务里面的微任务添加到微任务队列中,所有同步任务执行完后执行异步任务,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将宏任务从队列中调入主线程执行。这个过程一直循环执行(事件循环 Event loop)

6.案例题

例题一:

const promise1=new Promise((resolve, reject) => {
  console.log('promise1')
})
console.log('1', promise1);
// 'promise1' '1' Promise{<pending>}

解析:

  1. 从上到下,先执行到 new Promise函数, 实例化过程中执行的代码是同步任务 输出:'promise1'。

  1. 人后执行到同步代码 console.log('1', promise1); 这个时候的promise1 没有被 resolve 或者。reject,因此状态函数pending,输出:'1' Promise{<pending>}。

例题二:

const promise =new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
​

解析:

  1. 从上至下,先遇到new Promise,执行其中的同步代码1。

  1. 再遇到resolve('success'), 将promise的状态改为了resolved并且将值保存下来。

  1. 继续执行同步代码2。

  1. 执行到promise.then这个微任务,将其加入微任务队列。

  1. 执行同步代码4。

  1. 本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。

例题三:

setTimeout(function(){
    console.log('1');
});
new Promise(function(resolve){          
    console.log('2');
    resolve();
}).then(function(){         
    console.log('3');
}).then(function(){
console.log('4')
});         
console.log('5');
// 2 5 3 4 1

解析:

  1. 从上到下先执行到setTimeout,它是异步宏任务,放入到宏任务队列中。

  1. 执行到 new Promise 实例化过程中执行的代码是同步任务,放入主线程中执行,直接输出2。

  1. promise.then中的回调函数是异步微任务,放入到微任务队列。

  1. 执行到 同步任务 console.log('5'); 放入主线程中执行,直接输出5,至此同步任务中的同步任务执行完成。

  1. 从微任务队列中取出微任务到主线程中,输出3、 4,微任务队列为空,然后检查宏任务队列。

  1. 从宏任务队列中取出宏任务到主线程中,输出1,宏任务队列为空。

例题四:

setTimeout(()=>{
  new Promise(resolve=>{
  resolve();
  }).then(()=>{
  console.log('test');
  });
​
  console.log(4);
});
​
new Promise(resolve=> {
  resolve();
  console.log(1)
}).then( () => {
  console.log(3);
  Promise.resolve().then(() => {
    console.log('哈哈');
  }).then(() => {
    Promise.resolve().then(() => {
      console.log('呵呵')
    })
  })
})
console.log(2);
​
// 1 2 3 哈哈 呵呵 4 test

解析:

  1. 从上到下执行到setTimeout,是异步宏任务,将setTimeout的回调函数放入宏任务队列。

  1. 遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1。

  1. 而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中。

  1. 遇到同步任务console.log(2),输出2;主线程中同步任务执行完。

  1. 从微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中。

  1. 从微任务队列中取出任务a到主线程中,输出 哈哈。

  1. 从微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中。

  1. 从微任务队列中取出任务c到主线程中,输出 呵呵;微任务队列为空。

  1. 从宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空。

  1. 从微任务队列中取出任务d到主线程 ,输出test,微任务队列为空。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.怪兽

希望大家能够多多支持,我会继续

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值