宏任务和微任务

1.任务队列(首先要明白,宏任务和微任务都是指的异步任务,普通同步代码(如 console.log)会在当前执行栈中执行,而微任务和宏任务则涉及到异步操作和事件循环。)

简单来说,任务队列有两个,宏任务队列和微任务队列。代码执行时,宏任务进入到宏任务队列,微任务进入到微任务队列。那么哪些任务时微任务,哪些任务是宏任务呢?其实大部分的任务都属于宏任务。而微任务通常在代码运行时产生,通常是由Promise所创建的,Promise的then、catch、finally中的回调函数会作为微任务进入到微任务队列中。

例子:

setTimeout(() => {
    console.log(1);
})

Promise.resolve().then(() => {
    console.log(2);
})
解析:

  1. 首先,setTimeout会被放入宏任务队列(macrotask queue)中,等待主线程执行完毕后执行。由于setTimeout是宏任务,它的执行会在微任务之后。

  2. 接着,Promise.resolve().then()会创建一个微任务(microtask),因为then中的回调函数是微任务的一部分。微任务会在当前宏任务执行完毕之后立即执行。

现在,事件循环开始执行:

  • 首先,执行主线程的任务,但在这段代码中主线程并没有其他任务,因此会继续往下执行。

  • 然后,微任务队列中有一个任务,即Promise.resolve().then(() => { console.log(2); }),所以这个微任务会被执行。输出 2

  • 接下来,宏任务队列中有一个任务,即setTimeout(() => { console.log(1); }),所以这个宏任务会被执行。输出 1

总结:这段代码的输出是 21,因为微任务会在宏任务之前执行。

2.分类

宏任务(Macrotasks):

  1. setTimeout 和 setInterval:用于创建宏任务,指定的回调函数会被放入宏任务队列中。
  2. I/O 操作:诸如读写文件、操作数据库等异步任务。
  3. DOM事件:例如点击事件、输入事件等。
  4. AJAX请求的回调函数:AJAX完成时,其回调函数会被放入宏任务队列中。

微任务(Microtasks):

  1. Promise的then方法:Promise对象状态改变时,其回调会被放入微任务队列。
  2. MutationObserver:用于监听DOM变化,回调函数会被放入微任务队列。
  3. process.nextTick(在Node.js环境中):将回调函数放入当前执行栈的微任务队列中。

在事件循环中,每个宏任务执行完毕后,都会立即检查微任务队列,并执行其中的所有微任务。这确保微任务在宏任务之前执行,使得微任务具有更高的优先级。

3.难度升级

console.log('script start');

async function async1() {

    await async2();

    console.log('async1 end');

}

async function async2() {

    console.log('async2 end');

}

async1();

setTimeout(function() {

    console.log('setTimeout');

}, 0)

new Promise(resolve => {

    console.log('Promise');

    resolve();

})

.then(function() {

    console.log('promise1');

})

.then(function() {

    console.log('promise2');

})

console.log('script end');

先不要看正确答案,自己想想结果会是什么?

  1. console.log('script start');:输出 'script start'

  2. async1() 被调用:

    • 进入 async1 函数。
    • await async2(); 会执行 async2(),输出 'async2 end'
    • console.log('async1 end'); 被放入微任务队列。
  3. setTimeout(function() { console.log('setTimeout'); }, 0);:将 setTimeout 回调放入宏任务队列。

  4. new Promise(resolve => { console.log('Promise'); resolve(); }):输出 'Promise',并将两个 .then 的回调放入微任务队列。

  5. console.log('script end');:输出 'script end'

  6. 执行微任务队列:

    • 执行 console.log('async1 end');,输出 'async1 end'
    • 执行 .then(function() { console.log('promise1'); }),输出 'promise1'
    • 执行 .then(function() { console.log('promise2'); }),输出 'promise2'
  7. 执行宏任务队列:

    • 执行 setTimeout 的回调,输出 'setTimeout'

最终结果:

script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
 这里涉及到两个知识点:
1)promise是同步代码,.then才是异步任务

2)await的执行分两种情况:

  1. 如果await后面直接跟的为一个常量,比如:await 1,这种情况的话相当于直接把await后面的代码注册为一个微任务。可以简单理解promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise的时候,会注册promise.then()中的回调函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),在执行async1函数后面注册的微任务代码(promise1、promise2)。

  2. 如果await后面跟的是一个异步函数的调用,比如将上面的代码改成如下代码:(                该种情况下,执行完await并不先把await后面的代码注册到微任务队列中去,而是执行完await之后直接跳出async1函数,执行其他代码。然后遇到promise的时候,把promise.then中的回调函数注册为微任务。其他代码执行完毕后,需要回到async1函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中,注意此时微任务队列中是有之前注册的微任务的。所以这种情况会先执行async1函数之外的微任务(promise1、promise2),然后才执行async1内注册的微任务(async1 end);)

console.log('script start');

async function async1() {
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2 end');
    return Promise.resolve().then(() => { console.log('async2 end1'); })
}
async1();

setTimeout(function() {
    console.log('setTimeout');
}, 0)

new Promise(resolve => {
    console.log('Promise');
    resolve();
})
.then(function() {
    console.log('promise1');
})
.then(function() {
    console.log('promise2');
})

console.log('script end');

输出结果是:script start => async2 end => Promise => script end =>async2 end1 => promise1 => promise2 => async1 end => setTimeout

解析过程:

  1. console.log('script start');:输出 'script start'

  2. async1() 被调用:

    • 进入 async1 函数。
    • await async2(); 会执行 async2()
  3. async2() 被调用:

    • 执行 console.log('async2 end');,输出 'async2 end'
    • 返回一个 Promise,并在微任务队列中添加一个 .then(() => { console.log('async2 end1'); });。【!!!!!!!!注意此时跳出async1()的执行】
  4. setTimeout(function() { console.log('setTimeout'); }, 0);:将 setTimeout 回调放入宏任务队列。

  5. new Promise(resolve => { console.log('Promise'); resolve(); }):输出 'Promise',并将两个 .then 的回调放入微任务队列。

  6. console.log('script end');:输出 'script end'

  7. 此时回到async1(),将

     console.log('async1 end');加入微任务队列
  8. 执行微任务队列:

    • 执行 async2 中的 .then(() => { console.log('async2 end1'); });,输出 'async2 end1'
    • 执行 Promise 中两个 .then 的回调,输出 'promise1''promise2'
    • 执行console.log('async1 end');,输出‘async1 end’
  9. 执行宏任务队列:

    • 执行 setTimeout 的回调,输出 'setTimeout'

你可能感兴趣的:(javascript,前端,开发语言)