通过面试题让你搞懂,js任务管理

同步和异步、微任务与宏任务

JavaScript 语言的一大特点就是单线程,也就是说同一个时间只能处理一个任务。上一个任务没有完成,下一个就要一直等着。如果排队是因为计算量大, CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
为了解决这个问题就用到了异步和同步执行任务的技术。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步任务里,对于异步类型还有进一步的划分,那就是接下来我们要讲的微任务和宏任务,切记微任务比宏任务先执行
在这里插入图片描述

Event Loop(事件循环)

  1. 初始状态下,调用栈为空。微任务队列为空,只有宏任务队列里有一个script 脚本(整体代码)。这时首先执行并出队的就是 整体代码。
  2. 整体代码进入调用栈,进行异步和同步的划分。
  3. 同步任务直接被执行并且推出栈。异步任务被划分为同步任务和异步任务,分别被推进微任务对列和宏任务队列
  4. 等同步任务执行完毕之后(调用栈为空)再执行微任务,将微任务压入调用栈
  5. 等微任务执行完毕之后(调用栈为空) 再执行宏任务,将宏任务压入调用栈,直至调用栈再一次为空,一次轮回结束。
    在这里插入图片描述

宏任务

#浏览器Node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

微任务

#浏览器Node
process.nextTick
MutationObserver
Promise.then catch finally

然后我们来看几道面试题

  • 1.先来个简单的
console.log("1");
setTimeout(function () {
     console.log("2");
}, 0);
 Promise.resolve()
    .then(function () {
       console.log("3");
    }).then(function () {
       console.log("4");
    });
console.log("5");

输出:1 5 3 4 2

1.调用栈空、微任务对列空。整个脚本作为宏任务,被执行,执行同步输出 1 5
2.setTimeout宏任务放入宏任务队列。
3.Promise.then()微任务放入微任务执行队列。
2.同步任务执行完毕(调用栈为空),开始执行微任务,输出3 4
3.微任务执行完毕(调用栈为空),开始执行宏任务,输出 2

  • 2.注意和上面的区别
console.log('1')
setTimeout(function () {
     console.log("2");
}, 0);
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(function () {
      console.log("4");
 }).then(function () {
       console.log("5");
 });
console.log("6")

输出:1 3 6 4 5 2
答对了吗?
注意: Promise.then catch finally是微任务,但是romise构造函数中函数体的代码都是立即执行的。

new Promise(function (resolve) {
  console.log(1)
})

上面这段实例代码的 1 ,是直接输出的,属于同步任务,虽然它确实在 Promise 中

  • 3.复杂一点的
    function app() {
      setTimeout(() => {
        console.log("1-1");
        Promise.resolve().then(() => {
          console.log("2-1");
        });
      });
      console.log("1-2");
      Promise.resolve().then(() => {
        console.log("1-3");
        setTimeout(() => {
          console.log("3-1");
        });
      });
    }
    app();

输出:1-2 1-3 1-1 2-1 3-1

1.整个脚本作为宏任务被执行,首先执行同步任务输出:1-2
2.宏任务setTimeout,被放入宏任务队列等待执行
3.微任务 Promise.resolve().then,被放入微任务队列等待执行
4.同步任务执行完毕,调用栈此时为空,将微任务压入调用栈执行,输出1-3
5.又遇到一个宏任务,放入宏任务队列等待下一次轮询执行(和之前的宏任务不在同一个层级)
6.微任务执行完毕,调用栈此时为空,将宏任务压入调用栈执行,输出1-1
7.一次轮询结束
8.微任务执行,输出2-1
9.宏任务执行,输出3-1

关于async和await

promise的异步体现在 .then .catch .finally中,所以写在promise中的代码是同步执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。出现await之后呢?实际上await是一个让出线程的标志。await后面的表达式会立即执行,然后将后面代码放到微任务队列中,等待下一次轮询之后执行,然后就会跳出整个async函数来执行后面的代码。

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

相当于

async function async1() {
  console.log('async1 start')
  Promise.resolve(async2()).then(() => {
    console.log('async1 end')
  })
}

然后我们一起看个面试题练习一下吧

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(() => {
  console.log('setTimeout')
}, 0);
async1()
new Promise(resolve => {
    console.log('promise1')
    resolve()
  })
  .then(() => {
    console.log('promise2')
  })
console.log('script end')

在这里插入图片描述

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  new Promise(resolve => {
      console.log('promise1')
      resolve()
    })
    .then(() => {
      console.log('promise2')
    })
}
console.log('script start')
setTimeout(() => {
  console.log('setTimeout')
}, 0);
async1()
new Promise(resolve => {
    console.log('promise3')
    resolve()
  })
  .then(() => {
    console.log('promise4')
  })
console.log('script end')

在这里插入图片描述

async function async1() {
  console.log('async1 start')
  await async2()
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('setTimeout2')
  }, 0)
}
console.log('script start')
setTimeout(() => {
  console.log('setTimeout3')
}, 0);
async1()
new Promise(resolve => {
    console.log('promise1')
    resolve()
  })
  .then(() => {
    console.log('promise2')
  })
console.log('script end')

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个宏任务和微任务相关的面试题。 1. 什么是宏任务和微任务? 答:宏任务和微任务是 JavaScript 引擎用来管理异步任务的两种机制。 宏任务指的是在 JavaScript 引擎中排队等待执行的任务,这些任务通常是由浏览器或 Node.js 等宿主环境提供的,如定时器、事件回调等。 微任务指的是在当前任务执行完毕后需要立即执行的任务,这些任务通常是由 Promise 和 MutaionObserver 提供的,如 Promise.then()、Promise.catch()、Promise.finally() 和 MutationObserver。 2. 宏任务和微任务的执行顺序是怎样的? 答:宏任务和微任务执行顺序是不同的,它们都有自己的队列,而且微任务的优先级比宏任务高。 当一个宏任务被执行时,会先执行完所有的微任务,然后再继续执行下一个宏任务。如果在执行微任务的过程中产生了新的微任务,那么这些新的微任务会被插入到当前微任务队列的末尾,等待下一次执行。 举个例子:假设当前有一个宏任务 A,它包含三个微任务 X、Y 和 Z。当宏任务 A 被执行时,会先执行微任务 X,然后执行微任务 Y,最后执行微任务 Z,如果在执行微任务 Y 的过程中又产生了一个新的微任务 W,那么它会被插入到当前微任务队列的末尾,等待下一次执行。 3. 如何利用宏任务和微任务实现异步操作? 答:可以使用 Promise 和 async/await 来利用宏任务和微任务实现异步操作。 例如,使用 Promise 来发起一个异步请求: ```javascript function fetchData() { return fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { console.log('data:', data); }) .catch(error => { console.error('error:', error); }); } ``` 在这个例子中,fetchData 函数返回一个 Promise 对象,当这个 Promise 对象被 resolve 时,会执行一系列的微任务,包括打印数据到控制台。如果发生错误,会执行一个 catch 微任务。 另外,可以使用 async/await 来简化异步操作: ```javascript async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log('data:', data); } catch (error) { console.error('error:', error); } } ``` 这个例子中,使用 async/await 来编写异步代码,fetchData 函数会等待 fetch 请求的结果,在结果返回后再执行后续操作。如果发生错误,会抛出一个异常,可以通过 try/catch 块来处理。在执行 async 函数时,会创建一个微任务队列来管理异步操作,确保异步操作的执行顺序正确。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值