面试率 90% 的JS事件循环Event Loop,看这篇就够了!! !

本文介绍了如何通过系统化的学习资料提升Web前端技能,特别关注了事件循环中的定时器误差、视图更新机制,以及NodeJS中的事件循环和异步方法,强调了知识体系化学习的重要性。作者提供了《2024年最新Web前端全套学习资料》以帮助自学者避免浅尝辄止,鼓励加入技术交流社群共同成长。

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

定时器误差


事件循环中,总是先执行同步代码后,才会去任务队列中取出异步回调来执行。当执行 setTimeout 时,浏览器启动新的线程去计时,计时结束后触发定时器事件将回调存入宏任务队列,等待 JS 主线程来取出执行。如果这时主线程还在执行同步任务的过程中,那么此时的宏任务就只有先挂起,这就造成了计时器不准确的问题。同步代码耗时越长,计时器的误差就越大。不仅同步代码,由于微任务会优先执行,所以微任务也会影响计时,假设同步代码中有一个死循环或者微任务中递归不断在启动其他微任务,那么宏任务里面的代码可能永远得不到执行。所以主线程代码的执行效率提升是一件很重要的事情。

在这里插入图片描述

一个很简单的场景就是我们界面上有一个时钟精确到秒,每秒更新一次时间。你会发现有时候秒数会直接跳过 2 秒间隔,就是这个原因。

视图更新渲染


微任务队列执行完成后,也就是一次事件循环结束后,浏览器会执行视图渲染,当然这里会有浏览器的优化,可能会合并多次循环的结果做一次视图重绘,因此视图更新是在事件循环之后,所以并不是每一次操作 Dom 都一定会立马刷新视图。视图重绘之前会先执行 requestAnimationFrame 回调,那么对于 requestAnimationFrame 是微任务还是宏任务是有争议的,在这里看来,它应该既不属于微任务,也不属于宏任务。

四、NodeJS 中的事件循环


JS 引擎本身不实现事件循环机制,这是由它的宿主实现的,浏览器中的事件循环主要是由浏览器来实现,而在 NodeJS 中也有自己的事件循环实现。NodeJS 中也是循环 + 任务队列的流程以及微任务优先于宏任务,大致表现和浏览器是一致的。不过它与浏览器中也有一些差异,并且新增了一些任务类型和任务阶段。接下来我们介绍下NodeJS 中的事件循环流程。

五、NodeJS 中的异步方法


因为都是基于V8 引擎,浏览器中包含的异步方式在 NodeJS 中也是一样的。另外 NodeJS 中还有一些其他常见异步形式。

  • 文件 I/O:异步加载本地文件。

  • setImmediate():与 setTimeout 设置 0ms 类似,在某些同步任务完成后立马执行。

  • process.nextTick():在某些同步任务完成后立马执行。

  • server.close、socket.on(‘close’,…)等:关闭回调。

想象一下,如果上面的形式和 setTimeout、promise 等同时存在,如何分析出代码的执行顺序呢?只要我们理解了 NodeJS 的事件循环机制,也就清楚了。

六、事件循环模型


NodeJS 的跨平台能力和事件循环机制都是基于 Libuv 库实现的,你不用关心这个库的具体内容。我们只需要知道 Libuv 库是事件驱动的,并且封装和统一了不同平台的 API 实现。

NodeJS 中 V8 引擎将 JS 代码解析后调用 Node API,然后 Node API 将任务交给 Libuv 去分配,最后再将执行结果返回给 V8 引擎。在 Libux 中实现了一套事件循环流程来管理这些任务的执行,所以 NodeJS 的事件循环主要是在 Libuv 中完成的。

在这里插入图片描述

事件循环各阶段


在 NodeJS 中 JS 的执行,我们主要需要关心的过程分为以下几个阶段,下面每个阶段都有自己单独的任务队列,当执行到对应阶段时,就判断当前阶段的任务队列是否有需要处理的任务。

  • timers 阶段:执行所有 setTimeout() 和 setInterval() 的回调。

  • pending callbacks 阶段:某些系统操作的回调,如 TCP 链接错误。除 timers、close、setImmediate 的其他大部分回调在此阶段执行。

  • poll 阶段:轮询等待新的链接和请求等事件,执行 I/O 回调等。V8 引擎将 JS 代码解析并传入 Libuv 引擎后首先进入此阶段。如果此阶段任务队列已经执行完了,则进入 check 阶段执行 setImmediate 回调(如果有 setImmediate),或等待新的任务进来(如果没有 setImmediate)。在等待新的任务时,如果有 timers计时到期,则会直接进入 timers 阶段。此阶段可能会阻塞等待。

  • check 阶段:setImmediate 回调函数执行。

  • close callbacks 阶段:关闭回调执行,如 socket.on(‘close’, …)。

在这里插入图片描述

上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段。这里也是与浏览器中逻辑差异较大的地方,不过浏览器不用区分这些阶段,也少了很多异步操作类型,所以不用刻意去区分两者区别。代码如下所示:

const fs = require(‘fs’);

fs.readFile(__filename, (data) => {

// poll(I/O 回调) 阶段

console.log(‘readFile’)

Promise.resolve().then(() => {

console.error(‘promise1’)

})

Promise.resolve().then(() => {

console.error(‘promise2’)

})

});

setTimeout(() => {

// timers 阶段

console.log(‘timeout’);

Promise.resolve().then(() => {

console.error(‘promise3’)

})

Promise.resolve().then(() => {

console.error(‘promise4’)

})

}, 0);

// 下面代码只是为了同步阻塞1秒钟,确保上面的异步任务已经准备好了

var startTime = new Date().getTime();

var endTime = startTime;

while(endTime - startTime < 1000) {

endTime = new Date().getTime();

}

// 最终输出 timeout promise3 promise4 readFile promise1 promise2

另一个与浏览器的差异还体现在同一个阶段里的不同任务执行,在 timers 阶段里面的宏任务、微任务测试代码如下所示:

setTimeout(() => {

console.log(‘timeout1’)

Promise.resolve().then(function() {

console.log(‘promise1’)

})

}, 0);

setTimeout(() => {

console.log(‘timeout2’)

Promise.resolve().then(function() {

console.log(‘promise2’)

})

}, 0);

浏览器中运行 每次宏任务完成后都会优先处理微任务,输出“timeout1”、“promise1”、“timeout2”、“promise2”。

NodeJS 中运行 因为输出 timeout1 时,当前正处于timers阶段,所以会先将所有 timer 回调执行完之后再执行微任务队列,即输出“timeout1”、“timeout2”、“promise1”、“promise2”。

上面的差异可以用浏览器和 NodeJS 10 对比验证。是不是感觉有点反程序员?因此 NodeJS 在版本 11 之后,就修改了此处逻辑使其与浏览器尽量一致,也就是每个 timer 执行后都先去检查一下微任务队列,所以 NodeJS 11 之后的输出已经和浏览器一致了。

nextTick、setImmediate 和 setTimeout


实际项目中我们常用 Promise 或者 setTimeout 来做一些需要延时的任务,比如一些耗时计算或者日志上传等,目的是不希望它的执行占用主线程的时间或者需要依赖整个同步代码执行完成后的结果。

NodeJS 中的 process.nextTick() 和 setImmediate() 也有类似效果。其中 setImmediate() 我们前面已经讲了是在 check 阶段执行的,而 process.nextTick() 的执行时机不太一样,它比 promise.then() 的执行还早,在同步任务之后,其他所有异步任务之前,会优先执行 nextTick。可以想象是把 nextTick 的任务放到了当前循环的后面,与 promise.then() 类似,但比 promise.then() 更前面。意思就是在当前同步代码执行完成后,不管其他异步任务,先尽快执行 nextTick。如下面的代码,因此这里的 nextTick 其实应该更符合“setImmediate”这个命名才对。

setTimeout(() => {

console.log(‘timeout’);

}, 0);

Promise.resolve().then(() => {

console.error(‘promise’)

})

process.nextTick(() => {

console.error(‘nextTick’)

})

// 输出:nextTick、promise、timeout

接下来我们再来看看 setImmediate 和 setTimeout,它们是属于不同的执行阶段了,分别是 timers 阶段和 check 阶段。

setTimeout(() => {

console.log(‘timeout’);

}, 0);

setImmediate(() => {

console.log(‘setImmediate’);

});

// 输出:timeout、 setImmediate

分析上面代码,第一轮循环后,分别将 setTimeout 和 setImmediate 加入了各自阶段的任务队列。第二轮循环首先进入timers 阶段,执行定时器队列回调,然后 pending callbacks和poll 阶段没有任务,因此进入check 阶段执行 setImmediate 回调。所以最后输出为“timeout”、“setImmediate”。当然这里还有种理论上的极端情况,就是第一轮循环结束后耗时很短,导致 setTimeout 的计时还没结束,此时第二轮循环则会先执行 setImmediate 回调。

再看这下面一段代码,它只是把上一段代码放在了一个 I/O 任务回调中,它的输出将与上一段代码相反。

const fs = require(‘fs’);

fs.readFile(__filename, (data) => {

console.log(‘readFile’);

ajax

1)ajax请求的原理/ 手写一个ajax请求?
2)readyState?
3)ajax异步与同步的区别?
4)ajax传递中文用什么方法?

ajax.PNG

前12.PNG

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{

console.log(‘readFile’);

ajax

1)ajax请求的原理/ 手写一个ajax请求?
2)readyState?
3)ajax异步与同步的区别?
4)ajax传递中文用什么方法?

[外链图片转存中…(img-ftbaVhQ2-1713106562294)]

[外链图片转存中…(img-rMY4O30H-1713106562294)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-cI9R6mSa-1713106562295)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值