js宏任务和微任务(异步和同步执行的顺序)探究

js宏任务和微任务(异步和同步执行的顺序)探究



前言

js属于单线程,也就是说对于一段js代码片段,只会顺序依次执行,都是同步执行的。而实际使用过程中有些任务执行时间长,有些任务执行时间短,这会导致页面加载时间过长并且出现空白页面的现象,非常影响用户体验。为了解决这个问题产生了宏任务和微任务。


一、什么是宏任务和微任务

广义上来说
宏任务包含:script(整体代码块)、setTimeOut、setInterval、setImmediate、I/O、UI rendering
微任务包含:promise、Object.observe、MutationObserver
也没有什么定义,只是将上面这些代码块分为宏任务和微任务。对于宏任务和微任务JS又做了不同的处理方式。

  • 先从两张图片了解一下宏任务和微任务的处理方式。
    在这里插入图片描述在这里插入图片描述
    这两张图片简要介绍了js任务执行流程以及宏任务和微任务执行流程。从这张图的理解来说:宏任务的优先级大于微任务的优先级,也就是说宏任务先执行,然后再执行微任务之后以此往复。

  • 但是在有些视屏教程中介绍js的宏任务和微任务时,讲到微任务的优先级大于宏任务

  • 我的理解是:这两种说法都对,只是从看的起点角度不同罢了。对于第一种介绍来说。是将起始script代码块看做宏任务,这样子代码执行一开始就是宏任务。宏任务执行结束了再执行微任务,以此往复;对于第二种介绍。其实是拋过了起始script代码块,起始就是同步任务块,顺序执行。因此这样来说微任务的优先级就大于了宏任务。

  • 哪个观点更好:我认为两个观点都好。第一种更严谨。从宏任务和微任务的分类来说。script整体代码块就是宏任务;但是对于学者来说,很容易忽略主线程scrpit也代表的是一个宏任务。所以第二种说法更易懂
    接下来从示例代码中更进一步了解宏任务和微任务的执行顺序。

二、图例解释

在这里插入图片描述

图例解释:js代码是依次同步执行的,如上图所示,当执行过程中遇到宏任务,宏任务不会立刻执行,先放在宏任务执行队列;遇到微任务,也不会立即执行,会放入微任务队列;只有遇到js的主线代码才会立即执行。当主线代码执行结束,会依次将微任务调用到主线js代码执行,微任务调用结束之后,再去宏任务队列依次调用宏任务到主线代码执行。直到宏任务执行结束。


三、示例代码

  • 例1:
<script type="text/javascript">
	setTimeout(function() {
		console.log("宏任务1")
	}, 0);
	Promise.resolve()
		.then(() => {
			console.log("微任务1")
		});
	console.log("js主线代码1")
	setTimeout(function() {
		console.log("宏任务2")
	}, 0);

	Promise.resolve()
		.then(() => {
			console.log("微任务2")
		});
	console.log("js主线代码2")
</script>

结果:
在这里插入图片描述

  • 例2:
<script type="text/javascript">
	setTimeout(function() {
		console.log("宏任务1")
	}, 0);
	new Promise(resolve => {
		console.log("js主线任务3")
			resolve();
		})
		.then(() => {
			console.log("微任务1")
		});
	console.log("js主线代码1")
	setTimeout(function() {
		console.log("宏任务2")
	}, 0);

	new Promise(resolve => {
		console.log("js主线任务4")
			resolve();
		})
		.then(() => {
			console.log("微任务2")
		});
	console.log("js主线代码2")
</script>

结果:
在这里插入图片描述
分析: 这两段代码的区别在与promise一个是new,一个直接调用方法。这里有一个误区:会以为new Promise对象的执行代码也放在微任务。事实上是,new对象是相当于主js线程立即执行的。只有then的代码表示异步(微任务)。这样也就解释了执行结果为什么是这样子的。

  • 例3:
<script type="text/javascript">
	setTimeout(function() {//代码段1
		console.log("宏任务1")
		Promise.resolve()	//代码段2
			.then(() => {
				console.log("微任务2")
			});
	}, 0);
	Promise.resolve()	//代码段3
		.then(() => {
			console.log("微任务1")
			setTimeout(function() { //代码段4
				console.log("宏任务2")
			}, 0);
		});
	console.log("js主线代码1") //代码段5
</script>

结果:
在这里插入图片描述

分析: 这段代码就有点复杂了。这一块就解释了图例中说的是一个层级的流程是这个样子的。 可结合文章第二张图片,理解执行顺序更简单。 先简单的分析执行流程:

  1. 首先执行遇到代码段1,宏任务,放入宏任务队列
  2. 遇到代码段3,微任务,放入微任务队列
  3. 遇到代码段5,非宏任务/微任务,执行结果,打印:js主线代码1
  4. 执行微任务队列,代码段3,打印:微任务1
  5. 遇到代码段4,宏任务,放入宏任务队列
  6. 此时这里的微任务执行结束,所以去宏任务队列去执行执行打印:宏任务1
  7. 遇到代码段2,微任务,放入微任务队列
  8. 此时当前宏任务执行结束。去执行微任务,代码段2,打印:微任务2
  9. 微任务执行结束,再去执行宏任务,代码段4,打印:宏任务2
  • 例4:
<script type="text/javascript">
	setTimeout(function() {//代码段1
		console.log("宏任务1")
		Promise.resolve()//代码段2
			.then(() => {
				console.log("微任务2")
			});
		setTimeout(function() {//代码段3
			console.log("宏任务3")

		},0);
	}, 0);
	Promise.resolve()//代码段4
		.then(() => {
			console.log("微任务1")
			setTimeout(function() {//代码段5
				console.log("宏任务2")

			}, 0);
			Promise.resolve()//代码段6
				.then(() => {
					console.log("微任务3")
				});
		});
	console.log("js主线代码1")
</script>

在这里插入图片描述

分析: 这个例子更复杂,可结合文章第二张图片,理解执行顺序更简单。 执行步骤如下:

  1. 遇到代码段1,宏任务,放入宏任务队列
  2. 遇到代码段4,微任务,放入微任务队列
  3. 打印:js主线代码1
  4. 执行所有微任务,打印:微任务1
  5. 遇到代码段5,宏任务,加入宏任务队列
  6. 遇到代码段6,微任务,加入微任务队列
  7. 重点:此时刚开始我以为会执行宏任务,但是实际上,是因为第6步又加入了一个微任务,此时微任务没有执行完,所以先执行微任务。即打印:微任务3
  8. 这是所有微任务执行完成。执行宏任务,即代码段1,打印:宏任务1
  9. 遇到代码段2,微任务,放入微任务队列
  10. 遇到代码段3,宏任务,放入宏任务队列
  11. 此时该宏任务执行完成,执行微任务,即代码段2,打印:微任务2
  12. 微任务执行结束,执行宏任务,即此时第一个宏任务就是代码段5,即打印:宏任务2
  13. 再去执行微任务,没有微任务执行宏任务,即代码段3,打印:宏任务3
  14. 至此,所有宏任务和微任务执行结束

总结

  1. 从定义上出发,宏任务优先执行与微任务。从理解角度出发,我认为微任务优先执行与宏任务这种说法比较好理解。
  2. 理解宏任务和微任务执行顺序的问题,可以结合文章第二张图片介绍的宏任务和微任务的执行流程(当然这图是网上搜的)
  3. 我的理解是,先执行同步代码,然后执行当前所有的微任务,然后执行一个宏任务,然后再执行所有的微任务。再执行一个宏任务。再执行所有的微任务。。。依次类推,执行结束。
  4. 当然执行宏任务和微任务内部同步代码也是先执行的

附:js定时器(setTimeOut)真的守时吗?

答案是否定的。定时器的执行不一定是等待一定时间后执行的。在图例解释中的图片可看出,setTimeOut是宏任务,当执行到setTimeOut时,先把setTimeOut交给定时器定时,当时间一到,放入宏任务队列去执行。 因此如果主线程执行时间过长,那么定时器是不会准时的。首先得等到主线程执行结束。如果主线程执行时间特别短,那么setTimeOut时间一到即立刻执行。
例如下代码块:

<script type="text/javascript">
	setTimeout(function() {
		console.log("定时器1")
	}, 2000);
	for (var i = 0; i < 1000000; i++) {
		console.log(" ")
	}
	setTimeout(function() {
		console.log("定时器2")
	}, 3000);
</script>

这里执行结果是:当循环结束时立刻执行定时器1,然后执行定时器2。
这里也可以看出:js代码也不是说代码解析到哪里就开始执行到哪里,也许是先解析代码片段,然后依次解析到两个定时器,一个同步代码片段,将定时器放在定时器队列中,然后立刻执行同步片段,结束之后执行定时器。,如果说边解析边执行的话,那么会在循环结束时立刻执行定时器1,等待3秒之后执行定时器2,然而结果并非如此。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

£漫步 云端彡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值