你不知道的JavaScript的事件循环

JavaScript的事件循环

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。这也与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题

JavaScript是一门单线程执行语言。这句话直接定义了JavaScript程序执行顺序的核心,单线程意味着代码需要一行一行按照顺序执行,如果遇到一个大的循环语句或者远程加载图片资源等等语句,整个程序都会出现假死现象,直到当前的任务执行完毕,才会继续执行下面的程序。为了解决上面的问题,于是出现了同步执行、异步执行(宏任务与微任务)。

同步任务非常常见:比如上面描述的JavaScript按序执行、页面渲染、远程加载JavaScript资源等等都是同步任务。

JavaScript的执行机制

在介绍JavaScript的异步任务前,我们先来了解一下JavaScript的执行机制。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。为了更好的理解这句话,请看下图及代码解释。

在这里插入图片描述

console.log(1) // 用a代表
setTimeout(() => { // 用b代表
  console.log(2) // 用c代表
}); 
console.log(3); // 用d代表

上述代码执行过程如下:

  • 因为JavaScript是单线程,执行栈按顺序执行上面代码,首先遇到同步代码a并将a压进执行栈,执行输出结果,并弹出该函数语句;
  • 任务队列继续执行,遇到b异步函数setTimeout,因为是异步函数所以立马从栈中弹出,并异步执行模块(Event Queue)事件队列中,等待执行。
  • 遇到同步函数d,执行方式同上面的a。
  • 现在执行栈中为空,没有同步函数需要被执行,则取任务队列中查看,如果有任务就会压入执行栈执行。此时任务队列中有函数c,则被压入栈中执行。
  • 如果后续的c中继续存在异步任务,则会继续被放入到任务队列中等待被执行。

整个循环的过程就是Event Loop(事件循环)

宏任务与微任务

任务队列(Event Queue)有的也叫(消息队列),里面的任务可分为宏任务(Macro-task)和微任务( Micro-task ) 。

宏任务(Macro-task):

  • setTimeout
  • setInterval
  • I/O
  • UI render
  • requestAnimationFrame

微任务(Micro-task) :

  • Promise.then()
  • Async/Await
  • MutationObserver

setTimeout和setInterval

首先我们先来看setTimeout,这个api我们一般是在执行异步任务时使用,比如想在3秒后执行一个console

setTimeout(() => {
  console.log('我被执行了哈!!!');
}, 3000);

大多数情况下,这句代码执行的效果和我们期望的基本一直,即3秒后控制台输出 我被执行了哈!!!

但是基本之外就是一符合的预期,有时候我们期望500ms执行一个操作,但是确在2s之后执行了;

setTimeout(() => {
  console.log('我执行了一个操作!!!');
}, 500);

for (let index = 0; index < 10000000; index++) {
  // 这里遇到一个循环语句,大概执行了2秒
}

这里我们解释一下上面的代码执行顺序及原理:

  • 首先遇到一个异步操作,需要将console.log('我执行了一个操作!!!');放入异步队列模块中注册,然后开始定时器开始计时。
  • 接着执行同步操作for循环语句,但是该语句执行的时间稍微有那么一点长,足足执行了2秒。
  • 前面的定时器计时500毫秒已经到了,console.log('我执行了一个操作!!!');进入Event Queue中等待被放入执行栈中执行,但是不幸的是,前面的for循环执行还没结束,现在只能等待,直到for循环执行完毕才被执行;
  • 执行完毕,这时的时间已经比预期的500毫秒超出好多。

其实setIntervalsetTimeout的执行类似,这里就不在累述。唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue

所以作为异步的setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了实际的时间间隔,参数实际上只是指定了把要执行的代码添加到JavaScript(浏览器UI)线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那要执行的代码就要等前面的任务完成后再执行。

requestAnimationFrame

requestAnimationFramewindow的方法,告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

requestAnimationFrame的用法其实与setTimeoutsetInterval类似,只是不需要设置时间间隔而已,且精度要优于后两者。

requestAnimationFrame的执行通常与浏览器屏幕刷新次数相匹配,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

Promise

先要说明的是new Promise()是同步任务,而.then则是异步微任务;

关于Promise与async、await的细节,请参考你不知道的 async、await 魔鬼细节这篇文章;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绝对零度HCL

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

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

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

打赏作者

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

抵扣说明:

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

余额充值