1.为什么JS的任务要分类为同步任务和异步任务
试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行回调函数的任务会发生什么?页面会像瘫痪一下暂停下来等待这些需要需要时间的代码执行完毕,基于此,又引入了异步任务。
同步任务:JS是单线程的,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务,同步任务不需要进行等待可立即看到执行结果,比如console、alert()
异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求(非阻塞代码)
2.例子1
示例一我们可以看到既有同步任务也有异步任务,那么在主线程执行的过程中就会把两个定时器中的回调函数注册到异步事件队列里面,最后输出1 2 3 4,但是为什么是第二个setTimeout先输出,很明显从代码中可以看到是因为等待的时间不同,但是如果是网络请求呢,那时间不像是定时器一样提前规定好的,为了解决这个问题设置了消息队列,也可以理解为事件监听器,就是看请求的话看谁的响应回res较快,就先执行谁
console.log(1) // 同步任务
setTimeout(() => { // 异步任务
console.log(4)
}, 2000)
setTimeout(() => { // 异步任务
console.log(3)
}, 1000)
console.log(2) // 同步任务
3.为什么异步任务中要设置消息队列(事件监听)
消息队列(事件监听器)可以监听异步任务的状态,异步任务完成时执行相应的回调函数返回结果,例如,Promise对象就是一个消息队列,它允许在异步操作完成后注册回调函数。当Promise的状态变为fulfilled(已完成)或rejected(已拒绝)时,这些回调函数就会被调用。如果已经可以执行回调就会将对应的任务放到事件队列中
3.例子2
示例一假设了两个网络请求,监听器会先监听到请求得到响应任务(第二个),那么会先执行第二个ajax的回调,所以下面这段代码的输出是1 2 3 4
第一个ajax是先比第2个ajax执行的,只不过第一个响应慢了,监听器会先监听到第二个请求得到响应
console.log(1) // 同步任务
ajax().then(() => { // 异步任务且假设3s后得到响应
console.log(4)
})
ajax().then(() => { // 异步任务且假设1s后得到响应
console.log(3)
})a
console.log(2) // 同步任务
4.为什么异步任务的事件中要区分宏任务和微任务
假设如果异步任务的回调是在操作dom,但是页面渲染也是一个任务,如果把dom渲染的任务排到异步任务的队尾,那么页面同样会出现瘫痪,所以js就规定一些可以插队完成的任务,这类型的任务称为微任务,比如说dom渲染,后面又引入promise.then,
微任务可以插队
ajax().then(() => { })promese().then(() => { })setTime(()=>)这些就是宏任务,每个宏任务都会存在微任务,宏任务任务完成时执行相应的回调函数就是微任务,像then(),dom渲染,计时器里面的回调
每个宏任务后面都会存在微任务,如下图所示,遇到一段代码,从上往下执行,遇到同步代码就直接执行,遇到异步任务就先放到宏任务队列(这个放的操作就是同步的),等同步代码执行完在回来执行,执行完同步,消息队列监听宏任务队列,任务完成时执行相应的回调函数,这个回调就是微任务,像then(),dom渲染,计时器里面的回调,事件里面回调,放到微任务队列,谁快就先执行谁,可以插队,如果 拿上面的例子举例,console.log(1)和console.log(2)作为同步任务最先执行,然后执行宏任务1,第一个ajax,事件监听器会判断出哪一个事件先返回结果最终将回调函数放到主线程执行栈
反正就是先执行同步代码,然后执行宏任务回调产生的微任务(异步任务分为宏任务、微任务)
定时器跟请求肯定时排最后的,根据事件
宏任务:网络请求、定时器、HTML解析、鼠标事件、键盘事件、执行主线程js代码和(new Promise(xxx)中xxx是同步代码)
微任务:dom渲染, promise.then,async-await,process.nextTick
例子3:下面的执行结果是1 2 3 4 5,原因是1 2 3是同步任务,4 5是异步任务,且4是微任务,console.log(2)执行结束后宏任务队列中的第一个任务的微任务就是console.log(4),第二个宏任务是定时器,所以4先一步注入到执行栈
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
resolve()
}).then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
})
console.log(3)
例子3:先看例子思考哪些是同步任务 哪些是异步任务 哪些是宏任务和微任务
console.log(1)
new Promise((resolve, reject) => {
console.log("请求即将进行")
setTimeout(() => {
console.log("已请求到数据")
resolve()
}, 1000)
}).then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 2000)
console.log(2)
// 执行结果
1
请求即将进行
2
已请求到数据
3
4
下图大概描述代码执行过程
例子4:例子4和例子5结合来看,我们知道了事件监听器的重要性,也可以把那俩个定时器看作是两个ajax请求,那么我们就能想到浏览器是怎么处理网络请求和同步代码之间的关系的
console.log(1)
new Promise((resolve, reject) => {
console.log("请求即将进行")//Promise的同步代码
setTimeout(() => {
console.log("已请求到数据")
resolve()
}, 2000)
}).then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 1000)
console.log(2)
// 与上一个例子的唯一区别是定时器设置的时间不同,根据上图可知,上述代码的执行结果为
1
请求即将进行
2
4
已请求到数据
3
例子5:这个例子需要了解ES6的知识,async和promise是一样的道理
console.log('script start')//主线程执行栈
async function async1() { // 第一个promise
await async2()//相当于new Promise((resove) =>{console.log('async2 end')})
console.log('async1 end')//相当于promise的then(微任务)
}
function async2() {
console.log('async2 end')
}
setTimeout(function() { // 第一个定时器
console.log('setTimeout')
})
async1()
new Promise(resolve => { // 第二个promise
console.log('promise1')//主线程执行栈
resolve()
}).then(function() {
console.log('promise2')
}).then(function() {
console.log('promise3')
})
new Promise(resolve => { // 第三个promise
console.log('promise4')
setTimeout(() => { // 第二个定时器
console.log(11111111111111)
resolve()
})
}).then(function() {
console.log('promise5')
}).then(function() {
console.log('promise6')
})
console.log('script end')
// script start async2 end promise1 promise4 script end async1 end
promise2 promise3 setTimeout 1111111111111 promise5 promise6
图解,将执行整个js代码视为宏任务1,这样就可以理解第一个微任务队列是如何产生的
总结:
宏任务:鼠标事件、键盘事件、网络请求、定时器、HTML解析、执行主线程js代码和new Promise(()=>{xxxx})中xxx是同步代码)
微任务:dom渲染, promise.then,async,process.nextTick,事件回调,计时器回调
宏任务包括同步异步任务,一般都会把宏任务的主线程js执行,再去每个宏任务寻找是否有微任务,有就执行微任务,没有就执行第二个宏任务,再去找自己的微任务........定时器是最后面的,如果宏任务里面的微任务是定时器,执行顺序按照上下顺序
// 首先执行主线程的同步代码,再去执行宏任务,
// 宏任务执行顺序是按照从上往下,就在当前宏任务寻找微任务执行
// 没有微任务就执行下个宏任务,定时器在最后或者请求,定时器或请求根据时间,
一般最后的肯定是即使跟请求,看谁快
输出什么 ?(事件循环)
const promiseA = Promise.resolve('1')
promiseA.then((res) => {
console.log('a:', res)
// return res; // 返回res的值给下一个.then()
}).then((res) => {
console.log('a2:', res)
})
const promiseB = Promise.resolve('2')
promiseB.then((res) => {
console.log('b:', res)
})
promiseB.then((res) => {
console.log('b2:', res)
})
先执行完第一次then ,1,2,2,undefined,要在promiseA 的第二个then拿到res,需要在前一个return,
then回调方法,什么时候会被挂载到微任务?什么时候会执行?
then回调方法的挂载时机主要取决于Promise对象的状态:当resolve或reject,then就挂载到微任务
执行完主线程的同步代码
交互类型的宏任务 延时类型的宏任务 谁的优先级高?
交互类型,但具体的执行顺序仍然取决于浏览器的实现和当前的任务队列状态,比如交互跟setTime的延迟时间等
某些情况下,如果延时队列中的任务已经到达其延迟时间,并且交互队列中没有其他任务在等待,那么延时队列中的任务也可能会立即执行。
js事件循环,有两个宏任务,两个微任务,事件循环结束还有几个宏任务微任务
执行栈为空时:事件循环会检查微任务队列是否有任务。如果有,则依次执行微任务,直到微任务队列为空。
执行微任务:执行完所有微任务后,事件循环会取出宏任务队列中的一个宏任务来执行。
执行宏任务:在宏任务执行的过程中,如果遇到异步任务(如setTimeout、Promise等),这些任务会被放入对应的队列中等待执行。
循环执行:执行完一个宏任务后,事件循环会回到第一步,继续检查并执行微任务队列中的任务。这个过程会不断重复,直到所有任务都被执行完毕。
分析您的问题
假设初始状态下有两个宏任务和一定数量的微任务(数量未具体说明,但我们可以假设存在),我们需要分析事件循环结束后宏任务和微任务的数量。
初始状态:两个宏任务在宏任务队列中,一定数量的微任务在微任务队列中。
第一次事件循环迭代:
执行第一个宏任务。
在这个宏任务执行过程中,可能会产生新的宏任务或微任务,但这些新任务会在当前宏任务执行完毕后才被添加到对应的队列中。
执行完第一个宏任务后,检查并执行所有微任务。
第二次及后续事件循环迭代(如果存在):
执行第二个宏任务。
同样,这个宏任务执行过程中也可能产生新的宏任务或微任务。
执行完第二个宏任务后,再次检查并执行所有微任务。
事件循环结束的条件:当宏任务队列和微任务队列都为空时,事件循环结束。
结论
宏任务数量:由于题目中只明确提到了初始时有两个宏任务,但没有说明这些宏任务在执行过程中是否会生成新的宏任务,因此我们无法确定事件循环结束时宏任务的确切数量。如果初始宏任务在执行过程中没有产生新的宏任务,那么事件循环结束时宏任务的数量为0;如果有新的宏任务被产生,则数量取决于这些新宏任务的具体情况。
微任务数量:在事件循环的每一次迭代中,所有微任务都会被执行完毕。因此,事件循环结束时微任务的数量一定为0。
综上所述,事件循环结束时宏任务的数量取决于初始宏任务是否在执行过程中产生了新的宏任务,而微任务的数量一定为0。
一个事件循环执行一个宏任务吗
在JavaScript的事件循环中,一个事件循环的迭代(或称为“一轮”)不仅仅执行一个宏任务(MacroTask),而是执行一个宏任务以及随后该宏任务执行过程中产生的所有微任务(MicroTask)。这个过程会重复进行,直到宏任务队列为空且没有更多的微任务需要执行。
具体来说,事件循环的每一次迭代(或“tick”)按照以下步骤进行:
从宏任务队列中取出一个宏任务:如果宏任务队列不为空,则取出队列中的第一个宏任务来执行。
执行宏任务:执行取出的宏任务。在宏任务执行的过程中,可能会遇到异步操作(如setTimeout、Promise等),这些异步操作会根据其类型被添加到相应的队列中(宏任务队列或微任务队列)。
执行微任务队列中的所有微任务:在宏任务执行完毕后,事件循环会检查微任务队列。如果微任务队列不为空,则依次执行队列中的所有微任务。这个过程会重复进行,直到微任务队列为空。
回到步骤1:执行完所有微任务后,事件循环会回到步骤1,检查宏任务队列是否还有任务需要执行。如果有,则取出下一个宏任务来执行;如果没有,则等待新的宏任务被添加到队列中。
这个过程会一直重复进行,直到宏任务队列和微任务队列都为空,且没有其他异步操作需要处理时,事件循环才会真正结束(在Web浏览器中,这通常意味着页面被关闭或浏览器标签页被关闭)。
因此,一个事件循环的迭代不仅仅执行一个宏任务,而是执行一个宏任务以及随后可能产生的所有微任务。这种设计允许JavaScript在保持单线程执行的同时,能够处理异步操作并响应外部事件。