浅谈javaScript中的宏任务与微任务

文章目录

  • 理解任务队列
    • 同步任务
    • 异步任务
  • 代码说明
    • 前提知识
    • 示例
  • 小测试


理解任务队列

同步任务

同步任务即主线程。同一时间只能做一件事,除此之外不能做其它的事情。
主线程中的任务做完才会去其它的任务队列(异步任务队列)里抓任务来做。

任务队列,我们拆分词语,可以分成任务队列两个词语。

  • 任务:js中运行时真正执行的计算,需要待办的事项
  • 队列:存储任务的数据结构是队列的形式(First Input First Output,FIFO)

任务队列就好比去医院排队的过程:

进入队列 执行任务 移除任务 执行完毕
排队 挂号 离开排队的队伍 就诊
  • 先排队的先挂号,后排队的后挂号
  • 先进入任务队列的先执行,后进入任务队列的后执行

这便是只存在同步任务的情况。

js就是单线程语言,js的代码若不进行特殊处理,其执行顺序一般按编写、调用的顺序执行。
.
.

异步任务

宏观上的执行顺序

比如秃鹫这类食腐动物,它们只在猎物死亡后才会去进食,在此之前,它们会静静等待其它的捕食者先进食。

即:

  • 同步任务优先于异步任务 // 异步任务在同步任务执行之后执行

种类
异步任务在js中大致为两种:

  • 宏任务
  • 微任务

既然种类不止一个,那么必然会有执行的优先级问题。
我们拿大人与小孩举例子,将吃饭看作为任务:

大人吃饭 小孩吃饭
宏任务 微任务

大人会饿,但如果小孩饿了,那么我们会优先满足小孩,先让小孩吃饭,然后大人再吃饭。

即:

  • 微任务优先于宏任务执行

.
至此,我们可以拥有大致的知识轮廓:
浅谈javaScript中的宏任务与微任务_第1张图片
浅谈javaScript中的宏任务与微任务_第2张图片
任务队列相当于工作台,同步任务优先于异步任务执行;异步任务中,微任务优先于宏任务执行。

代码说明

.

前提知识

首先,我们需要知道:

  • Promise的构造函数创建时是同步任务
  • Promise实际调用为微任务
  • 不同维度下:同步=>微任务=>宏任务 (同步=>异步)
  • 相同维度下:按执行的速度快慢

以下我们以then代表微任务,以setTimeout代表宏任务


示例

Step 1:纯同步

注意以下代码执行结果的顺序:

new Promise((resolve,reject)=>{
  console.log('同步执行:promise的构造函数')
})

console.log('同步执行:全局')


//输出结果: 
	同步执行:promise的构造函数
	同步执行:全局

我们可以理解为:

  console.log('同步执行:promise的构造函数')
  console.log('同步执行:全局')

.
.


Step 2: then微任务 + 同步

我们使用then获取resolve传递的数据:

new Promise((resolve, reject) => {
  resolve('异步执行:then方法')
  console.log('同步执行:promise的构造函数')
})
.then(
  successCallbacks =>console.log(successCallbacks)
)

console.log('同步执行:全局')


// 输出结果:
	同步执行:promise的构造函数
	同步执行:全局
	异步执行:then方法

此时,无论有多少个同步的代码,then方法永远在最后执行。
无论作业留多少,我永远在最后写。
.
.

当我们连续使用then的时候:

new Promise((resolve, reject) => {
  resolve('异步执行:then方法')
  console.log('同步执行:promise的构造函数')
})
  .then(
    successCallbacks => console.log(successCallbacks)
  )
  .then(
    () => console.log('then * 2')
  )
  .then(
    () => console.log('then * 3')
  )
console.log('同步执行:全局')

//输出结果:
同步执行:promise的构造函数
同步执行:全局
异步执行:then方法
then * 2
then * 3
...

这时我们已经可以简单的对返回值连续进行处理。
.
.


Step 3:setTimeout宏任务 + 同步

  setTimeout(() => {
    console.log("异步宏任务执行")
    }, 0);
  console.log('同步执行:全局')
  for (let i = 0 ; i < 10000 ; i++){
    console.log('n');
  }

执行结果:
在这里插入图片描述
凡是异步任务,均在同步任务执行之后才执行。

(这里顺便一提,setTimeout的delay即便设置为0,也实际上被设置为0.004s,即最低为4ms。)
.
.


step 4:同步+异步

当宏任务与微任务同时出现时,微任务优先。大人一般先照顾孩子,再照顾自己。

new Promise((resolve, reject) => {
  resolve('异步执行:then方法')
  setTimeout(() => {
    console.log('异步执行:setTimeout');
  }, 0);
  console.log('同步执行:promise的构造函数')
})
  .then(
    successCallbacks => console.log(successCallbacks),
  )
console.log('同步执行:全局')


//执行结果:
	同步执行:promise的构造函数
	同步执行:全局
	异步执行:then方法
	异步执行:setTimeout

Promise回调中,我们无论怎么变换resolve()/reject()、 setTimeout()、console.log()的代码顺序,执行结果都不会发生变化
.
.


step 5 : 深入理解

查看以下代码:

new Promise((resolve, reject) => {
  console.log('同步执行:promise的构造函数')
  setTimeout(() => {
    resolve('异步执行:then方法')
    console.log('异步执行:setTimeout');
  }, 0);
})
  .then(
    successCallbacks => console.log(successCallbacks),
  )
console.log('同步执行:全局')

resolve放在了setTimeout里,此时的执行结果:

同步执行:promise的构造函数
同步执行:全局
异步执行:setTimeout
异步执行:then方法

原因是:resolve()想要执行,必须先执行setTimeout()。换句话说,执行setTimeout()之后resolve才能被执行。

当执行setTimeout()的时候,此时同步任务队列已经执行完同步代码,主线程将setTimeout()宏任务放入任务队列中。

此时的resolve相当于在宏任务下的子任务。

在setTimeout()里,语句console.log('异步执行:setTimeout') 相当于setTimeout里的同步代码,
而resolve()则是其异步代码。

拿博客举例子:
我们必须先打开博客的网页显示出博客的页面才能看博客里的文章

new Promise((resolve,reject)=>{
	打开博客的网页(()=>{
		resolve(看博客里的文章)
		console.log(显示出博客的页面)
	})
})

小测试

最后来一个小测验,判断代码执行的顺序:


setTimeout(() => {
  setTimeout(() => {
    console.log("内层的定时器");
  }, 0);
  new Promise(resolve => {
    console.log("定时器内的Promise");
    resolve();
  }).then(() => {
    console.log("定时器内的then");
  });
  console.log("最外层的定时器");
}, 0);
new Promise(resolve => {
  console.log("全局的Promise");
  resolve();
}).then(() => {
  console.log("全局的then");
});
console.log("全局的log打印");


.
.
.

答案如下:

全局的log打印
全局的then
定时器内的Promise
最外层的定时器
定时器内的then
内层的定时器

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