前言
最近在部门内分享讨论关于浏览器的各个方面,再加上这段时间一直都在深度使用ChatGPT,学习各种玩法,突发奇想,要不结合一下AI来重新学习一下前端的各个经典模块,于是有了这篇文章
PS:专门开了一个专栏《AI进阶前端经典知识》,希望能持续更新~
主题方向
我们这个专栏系列的第一篇便是关于事件循环,怎么样,够经典吧? 我会从以下几个方向来分享一下有关心得和思考:
- 浏览器的进程模型?
- 和前端关系最大的“渲染主线程”怎么工作?
- 事件循环的具体流程?
- JS异步的思考?
- 任务优先级的思考?
浏览器的进程模型
在剖析浏览器的进程模型之前,我们还是先来重新了解一下何为进程?何为线程?
相信我们都对进程、线程一点都不陌生,都有自己的理解,那我们来看看和ChatGPT之间的对话(节省篇幅,只会放出部分对话截图,省略之前的训练对话以及多余的无关对话)
1、进程和线程
所以我们得出我们的第一部分的理论:
2、浏览器中有哪些进程 or 线程
在继续下去之前,我们先来思考一个问题:“浏览器是一个什么样的应用程序?”
我们依然来和ChatGPT来对话:
ChatGPT:
OK,我们可以看到他超前回答了我们的问题,那根据他的回答,我们应该可以得出我们上面的第一个问题的答案:浏览器是一个多进程、多线程的应用
为了验证这一答案,我们可以打开Chrome的任务管理器:
那从任务管理器中我们就能很清晰的看到浏览器的进程模型是由哪些主要的进程组成的:
- 浏览器进程
- 网络进程
- GPU进程
- 插件进程
- ······
那这个时候就有一个问题了:浏览器的渲染进程去哪儿了? 我们再看打开了多个页签之后的情况:
我们可以看到浏览器为每一个新打开的页签都开启了一个新的进程,其实这就是我们在找的渲染进程,每一个新的页签都会有一个自己单独的渲染进程。(其实仔细看ChatGPT的回答中已经给出了答案)
同样我们想要确定无疑这个答案也很容易,比如我们来看官方文档的描述: (翻译可能不准,建议直接见英文原文,链接放末尾)
我们其实可以看到的是 Chrome目前默认的模型就是:“Process-per-site-instance“,即一个站点实例一个进程(就是一个页签一个进程的意思)
PS:那我们还可以看到其实Chrome还有一个模型是“Process-per-site”:一个站点一个进程,特点就是将同一个站点下所有的实例全都合并为一个进程,那这样以后是不是就可以消除掉很多进程了?毕竟现在如果标签页开多了还是很占内存的
那到此,我们应该对浏览器的进程模型有了一个基础的认识了,接下来就要进入和我们前端息息相关的“渲染进程”的分析了~
渲染主线程
我们之前了解到线程是进程中的执行单元,那一个进程中至少有一个称之为“主线程”,而我们今天要说的事件循环就发生在渲染进程中,那我们就下来就要来聊一下“渲染主线程”
我们先来了解一下渲染主线程主要负责的工作:
这个对于我们前端开发来说应该还是比较熟悉的了~
那渲染主线程要同时负责这么多任务,那是怎么去进行调度的呢? 比如我们可能会思考某些问题🤔:
- 比如正在执行一个JS函数的时候,这个时候我点击了一个按钮,那这个时候渲染线程应该先执行哪个?
- 再比如正在执行JS函数的时候,一个定时器的时间计时到了,那又该先执行哪个?
- 等等等等
那我们应该知道,渲染主线程采用的方法就是最朴素的方式:排队
那么我们就来看看他的排队机制吧~
事件循环(消息循环)
我们先来看chatGPT的回答:
然后我们再来看一张简单的图:
然后我们来简单描述一下这张图的流程:
- 首先在一开始,渲染主线程进入一个无限循环的方式
- 然后每一次的循环都会去检查消息队列当中是否存在任务,如果有任务,那么将会从队列的前面依次取出任务执行;如果没有,那么主线程休眠
- 其他线程也可以随时像消息队列中去添加任务,甚至其他进程也可以,比如浏览器进程监测到用户某些交互的时候,都能向消息队列末尾添加任务
- 不断重复,不断循环,不断运行
那么其实,这一整个过程便可以称之为“事件循环”
那我们该怎么来验证这一个流程呢?
我们简单的找一找Chrome的源码文件,发现:
我们可以看到在一个名为message_loop(消息循环)的里面可以找到事件循环的开始,就是进入一个无限循环
PS:这里我们可以注意到两个名词:事件循环(W3C) 和 消息循环(Google),其实这就是两个相关方的叫法有所不同,其实本质是一样的
那到此,其实我们今天要了解的事件循环的主要流程就有了,那么接下来就会引发一些其余的思考:
- 为什么说JS是一门单线程的语言?JS的执行为什么又能阻碍页面的渲染?
- 我们所说的任务有优先级吗?
那么我们接下来就进入到最后几个问题的讨论吧~
JS异步的思考
每一个前端在学习JS的时候都会知道的一个结论:那就是JS是一门单线程的语言,那我们有没有想过为什么要这么说呢?
其实在我们前面就已经有了结论了,原因很简单,不就是因为JS是运行在浏览器的渲染主线程的吗?
因为渲染主线程只有一个,那所以JS不就是单线程语言了嘛
那同样,渲染主线程还负责页面渲染,任务都是需要排队的,那JS的执行自然就能阻碍页面的渲染,是不是很容易就理解呢?
那回答了这个问题,我们再来想想我们在代码执行的过程中是不是会碰到很多无法立即处理的问题,比如:
- 加了计时器函数:setTimeOut、setInterval
- 网络请求完成之后才需要执行的函数
- 用户操作完成之后需要执行的任务
以计时器为例,我们再来看一个图:
我们发现当有计时器存在时,会先去开启一个线程来监听计时,结束之后会加入到消息队列当中,然后主线程会立即开始下一项任务不会直接等待
试想一下,如果是同步的方式,那这个时候主线程就要等待计时器计时完成然后执行计时器当中任务,再接下去做其他的任务,那整个流程是不是就要处于阻塞状态甚至“卡死状态”?
这也就是为什么是使用异步的方式了~
任务的优先级
老规矩,我们还是先问ChatGPT:
果然,很熟悉的词语:宏任务和微任务,我们之前在理解事件循环的时候也是这些熟悉的逻辑和关键词
但是随着浏览器越来越复杂,似乎也不能再仅凭宏任务队列来概括除了微任务队列的其他所有,那么根据W3C最新的说法:
- 对于每个事件循环,每个任务源都必须与特定的任务队列相关联:也就是说每个任务都有一个任务类型,同一个类型的任务必须在一个队列
- 每个事件循环都有一个microtask queue,这是一个microtasks队列 ,最初是空的。微任务是指通过队列微任务算法创建的 任务的通俗方式。 :浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行
具体我们可以参考:(翻译可能不准,具体参考英文原版,链接放末尾)
那在我们开发过程中,哪些任务队列比较常见重要呢?以Chrome为例,优先级由高到低:
- 微队列:用于存放需要最快执行的任务,优先级「最高」
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」
- 延时队列:用于存放计时器到达后的回调任务,优先级「中」
那我们最后应该得出的结论应该是:一个任务队列中的任务是没有优先级的,遵从“先入先出”的原则;但是任务队列之间是有优先级的,而其中微队列的优先级最高!
PS:在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。并且不同浏览器,甚至同一浏览器不同版本的表现形式都会有所差异
我们最后再来看一张图:
OK,那以上就是关于事件循环的全部内容啦,AI好用,但是有些内容也需要甄别哦,不过不得不说,ChatGPT是真香!
文末福利
全民AI时代,抱团取暖,我和小伙伴共建了一个AI的知识库。
里面包含了:AI最新资讯、AI应用、工具、AI实用教程、AI赚钱案例等。
获取方式:关注微信公众号【CoLabify】回复关键词【AI福利】即可~
另有同名小程序定期更新各种实用工具噢~