0


终于搞懂了JS中的事件循环,同步/异步,微任务/宏任务,运行机制(附笔试题)

js是单线程语言

进程,即资源分配的最小单位,拥有独立的堆栈空间和数据存储空间
线程,即程序执行的最小单位,一个进程可以包括多个线程
如果多进程,同时操作DOM,那么后果就不可控了
例如:对于同一个按钮,不同的进程赋予了不同的颜色,到底该怎么展示
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务 (synchronous)

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

异步任务(asynchronous)

异步任务指的是,不进入主线程,而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

事件循环(Event Loop)

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

宏任务(macrotask)

当前调用栈中执行的代码成为宏任务。 包括:script 中代码、setTimeout、setInterval、I/O、UI render

微任务(microtask)

当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件
包括:process.nextTick、MutationObserver、Promise.then

promise

Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的

new Promise(function(resolve) {
    console.log('promise');  // 此处立即执行
    resolve();
}).then(res=>{
    console.log('rosolve')  // 加入到微任务(microtask中)中
})

Async/await

而在async/await中,在await出现之前,其中的代码也是立即执行的。 很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。 await后面的表达式会先执行一遍,将await后面的代码加入到微任务microtask中,然后就会跳出整个async函数来执行后面的代码。 因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask

async function async1() {
    console.log('async1 start'); //立即执行
    await async2(); //立即执行await后面的表达式
    console.log('async1 end'); //将await后面的代码加入到微任务(microtask中)中
}

题一

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
*/

解析:
1.事件循环从宏任务(macrotask)队列开始,宏任务队列中,只有一个script(整体代码)任务,执行整个代码块
2.遇到2个定义的async函数,继续往下走,遇到console语句,直接输出 'script start'
3.script任务继续向下执行,遇到async1()函数,async函数中在await之前的代码是立即执行的,所以输出 'async1 start',然后执行async2()函数,输出 'async2',将 ‘console.log('async1 end')’分配到microtask队列中
4.script任务继续往下执行,遇到Promise,Promise中的代码是被当做同步任务立即执行的,所以输出 'promise1',然后执行 resolve,将 'promise2' 分配到microtask队列中
5.script任务继续往下执行,最后只有一句输出了'script end',至此,全局任务就执行完毕了
6.每次执行完一个宏任务之后,会去检查是否存在 microtask;如果有,则执行 microtask 直至清空 microtask Queue。因此在script任务执行完毕之后,开始查找清空微任务队列
7.microtask队列中,Promise 队列中有两个任务async1 end和promise2,因此依次输出async1 end 、promise2,所有的 microtask 执行完毕之后,表示第一轮的循环就结束了
8.第二轮循环依旧从宏任务队列开始。此时宏任务中只有一个 setTimeout,取出直接输出即可,至此整个流程结束

题二

和题一不同的是async2()函数内部也写成了Promise,Promise中的代码是被当做同步任务立即执行的,所有会输出 'promise1','promise2'则被分配到microtask队列中,因此输出完 'script end'后,会相继输出 promise2, async1 end ,promise4,setTimeout

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

/*
    script start
    async1 start
    promise1
    promise3
    script end
    promise2
    async1 end
    promise4
    setTimeout
*/

题三

解析:
在输出为promise2之后,接下来会按照加入setTimeout队列的顺序来依次输出,通过代码我们可以看到加入顺序为3 2 1,所以会按3,2,1的顺序来输出。

sync function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout2')
    },0)
}
console.log('script start');

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

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

/*
    script start
    async1 start
    promise1
    script end
    promise2
    setTimeout3
    setTimeout2
    setTimeout1
*/

题四

async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

/*
    script start
    a1 start
    a2
    promise2
    script end
    promise1
    a1 end
    promise2.then
    promise3
    setTimeout
*/
解析:
    1.  Promise.resolve().then(() => {
          console.log("promise1");
        });
        a1();
    
    'promise1'也是被分配到microtask队列中的,而且是在a1()函数执行前,先分配的,所以在 'script end'输出后,会先输出 'promise1' 然后在依次输出
    
    2.  let promise2 = new Promise((resolve) => {
          resolve("promise2.then");
          console.log("promise2");
        });
    这里会直接输出"promise2", 'resolve'不同于'await',不会阻止后面的执行
    

结尾

如果此篇文章对你小伙伴所帮助的话,小伙伴们手动点赞啊

后续会继续更新前端相关文章,感谢小伙伴支持

axios二次封装详解https://blog.csdn.net/qq_56989560/article/details/125032315?spm=1001.2014.3001.5501

参考文献:

https://juejin.cn/post/6921606187116920845#heading-13https://juejin.cn/post/6921606187116920845#heading-13


本文转载自: https://blog.csdn.net/qq_56989560/article/details/125060881
版权归原作者 Mr.指尖舞者 所有, 如有侵权,请联系我们删除。

“终于搞懂了JS中的事件循环,同步/异步,微任务/宏任务,运行机制(附笔试题)”的评论:

还没有评论