彻底搞通Js中的事件循环

基本原理

js是单线程的,避免了多个线程同时操作同一个DOM产生矛盾的问题。但浏览器

Js引擎执行一段代码块,会将同步任务放到主线程排队执行,异步任务放到任务队列,异步任务又分宏任务和微任务,比如常见的setTimeout / setInterval 为宏任务,promise.then/Mutation Observe为微任务(在这里需要注意的是,尽管promise.then是微任务,但是其注册是在状态确认后同步的,具体后面通过示例讲解)。当主线程执行完毕后会去检查微任务队列,微任务队列中所有的任务都会被依次取出来执行,直至为空,然后再检查宏任务队列(注意一个宏任务里面的微任务执行完才会执行下一个宏任务),这个过程是循环不断的,这种运行机制称为Event Loop(事件循环)

在事件循环中,每进行一次循环操作称为tick
在这里插入图片描述

宏任务微任务示例

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  // 排到宏任务队列
  setTimeout(()=>resolve(5)) 
  console.log(‘attach’)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

过程:
执行打印1
放入宏任务setTimeout
执行new Promise,打印4,放入宏任务setTimeout,打印attach
放入宏任务setTimeout
执行打印7

此时打印了1,4,attach,7,
微任务队列:无
宏任务队列:依次排列的三个宏任务setTimeout

第一个tick完成

执行第一个宏任务:
打印2
将then中的回调函数放入微任务
再执行微任务队列,打印3

第二个tick完成

执行第二个宏任务:
promise resolve
将then中的回调函数放入微任务
再执行微任务队列,打印5

第三个tick完成

执行第三个宏任务:
打印6

第四个tick完成

tips

需要注意:then的回调监听最新Promise对象的resolve执行后才会注册进微任务队列,之后的then回调都依赖于前一个then中的代码执行结束。

then执行顺序的问题

请看以下题目:

new Promise((resolve,reject)=>{
	console.log("promise1",1)
	resolve()
}).then(()=>{
	console.log("then11",2)
	new Promise((resolve,reject)=>{
		console.log("promise2",3)
		resolve();
	}).then(()=>{
		console.log("then21",4)
		new Promise((resolve,reject)=>{
			console.log("promise3",5)
			resolve();
		}).then(()=>{
			console.log("then31",7)
		}).then(()=>{
			console.log("then32",9)
		})
	}).then(()=>{
		console.log("then22",8)
	})
}).then(()=>{
	console.log("then12",6)
})

输出123456789,需要注意的点:then的回调监听最新Promise对象的resolve执行后才会注册进微任务队列,之后的then回调都依赖于前一个then中的代码执行结束。
来看下面一个例子:

new Promise((resolve,reject)=>{
	console.log("promise1",1)
	resolve()
}).then(()=>{
	console.log("then11",2)
	**return** new Promise((resolve,reject)=>{
		console.log("promise2",3)
		resolve();
	}).then(()=>{
		console.log("then21",4)
		new Promise((resolve,reject)=>{
			console.log("promise3",5)
			resolve();
		}).then(()=>{
			console.log("then31",7)
		}).then(()=>{
			console.log("then32",9)
		})
	}).then(()=>{
		console.log("then22",8)
	})
}).then(()=>{
	console.log("then12",6)
})

结果是:123457896,在上题的基础上,新增了一个return。
再看一个巩固一下:

new Promise((resolve,reject)=>{
	console.log("promise1",1)
	resolve()
}).then(()=>{
	console.log("then11",2)
	return new Promise((resolve,reject)=>{
		console.log("promise2",3)
		resolve();
	}).then(()=>{
		console.log("then21",4)
		return new Promise((resolve,reject)=>{
			console.log("promise3",5)
			resolve();
		}).then(()=>{
			console.log("then31",7)
		}).then(()=>{
			console.log("then32",9)
		})
	}).then(()=>{
		console.log("then22",8)
	})
}).then(()=>{
	console.log("then12",6)
})

加入async await

只需要记住一点:每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。

具体请参考:https://blog.csdn.net/roamingcode/article/details/85052590

SetTimeout/SetInterval时间不准的问题

setTimeout时间不准

我们添加一个定时器后,在间隔我们设定时间后,将函数代码放到宏任务队列中,等主线程及微任务队列空闲时,再去执行宏任务队列。如果主线程及微任务队列和宏任务队列前面有很多耗时任务,那么就会延迟执行定时器任务。

SetInterval时间不准

对于setInterval定时器代码规则地插入队列中(仅当没有该定时器的其他代码时,才将定时器代码添加到队列中,避免了代码的连续执行)。如果定时器代码或者任务队列里面有耗时任务,执行时间超过了设定的时间,那么下一次的代码得等到当前执行完后再立即执行,从而造成不准时。

解决办法:
递归调用setTimeout来实现setInterval,这样就能保证至少间隔准时。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值