一文讲解浏览器运行渲染机制、JS任务队列及事件循环

简介: 你是不是有过以下困难:• 多个方法互相嵌套,但是最终还是蒙对了• 不是很明白为什么浏览器有时候会卡死• 事件循环好像知道那么点,但是就是讲不出来为啥• ……本篇文章就把你的问题给一一解答,当然这些东西想完弄清楚,肯定离不开进程,线程,浏览器内核,渲染,事件循环,任务队列等,我们就一个一个的来看,它们到底是怎么工作的。

你是不是有过以下困难:

  • 多个方法互相嵌套,但是最终还是蒙对了
  • 不是很明白为什么浏览器有时候会卡死
  • 事件循环好像知道那么点,但是就是讲不出来为啥
  • ……

本篇文章就把你的问题给一一解答,当然这些东西想完弄清楚,肯定离不开进程线程浏览器内核渲染事件循环,任务队列等,我们就一个一个的来看,它们到底是怎么工作的。


进程和线程


举个例子,一个工厂,它有自己独立的资源,工厂和工厂之间相互独立,各自做各自的事情。一个场子可以有很多工人,工人可以 单个作业 也可以 协同作业,工人做的事情,都只会在自己的工厂内,并且共享这个工厂的空间。


我们现在把概念放到进程上,一个进程就相当于一个工厂,工厂里的资源就相当于系统分配的独立内存,多个工厂各自做各自的事情就相当于进程之间相互独立,一个工厂有很多工人就相当于一个进程可以有很多线程,工人的作业就相当于线程完成任务,工人共享这个工厂的空间,就相当于一个进程下面的线程之间可以共享程序的内存。

M_`MJARLHJGJ}X3(SK)BN5T.png


windows 的任务管理器中 CPU 和 内存 可以把每个进程的占用看的很清楚,当然 Mac OS 从活动监视器中也可以看到。所以:进程是 cpu 资源分配的最小单位,线程是cpu调度的最小单位

大家所说的多线程和单线程,都是只在一个进程内的多和单!


浏览器


构成

前提:页面是跑在浏览器上的,也就是说浏览器是页面的载体,浏览器会制定一套规则,页面满足了这个规则然后才可以在到浏览器上正常运行。

浏览器本质上其实是一个软件,它运行在一个操作系统上(windows 或 MacOS 或 其他),一般来说操作系统会开一个端口去运行这个软件,也就是为这个进程分配了CPU,内存 和 磁盘空间等。

那浏览器是单进程还是多进程呢?我们看一下:

可见它是个多个进程的浏览器!

在 Chrome 多进程架构里,它包括了四个进程:

  • Browser进程(负责地址栏、书签栏、前进后退、网络请求、文件访问等)
  • Renderer进程(负责一个Tab内所有和网页渲染有关的所有事情,是最核心的进程
  • GPU进程(负责GPU相关的任务)
  • Plugin进程(负责Chrome插件相关的任务)

如果你打开它的任务管理器,你会发现:

上图我们可以看出:一个标签页就是一个进程,甚至一个扩展程序就是一个进程!在浏览器中打开一个网页就相当于新开了一个进程。

但是:在这里浏览器有自己的优化机制,有时候打开多个标签页,进程会合并,所以每一个标签页对应一个进程不是绝对的。

这样的多进程分配的好处是:

  • 如果一个页面挂了,不会影响其他页面,甚至影响到整个浏览器
  • 避免安装的三方插件等影响了浏览器全局
  • 多进程充分利用了多核的优势
  • 把插件,扩展程序等全部隔离,提高稳定性

当然,缺点很就明显了,内存消耗大,确实有点像空间换时间的意思。


请求,响应

接下来我们看下浏览器是如何通过输入内容来请求成功的。

  1. 当用户在地址栏输入内容时,UI线程首先问的是“这是搜索查询还是URL?”。在Chrome浏览器中,地址栏也是搜索输入字段,因此UI线程需要解析并决定是将您发送到搜索引擎还是请求的网站。

  2. 当用户按下Enter键时,UI线程会发起网络调用以获取网站内容。加载微调框显示在选项卡的角上,并且网络线程通过相应的协议(例如DNS查找和为请求建立TLS连接)。
    此时,网络线程可能会收到服务器重定向标头,例如HTTP301。在这种情况下,网络线程与服务器正在请求重定向的UI线程进行通信。然后,将启动另一个URL请求。

  3. 一旦有响应了,网络线程将在必要时查看流的前几个字节。响应的Content-Type标头应说明它是什么数据类型,但是由于可能丢失或错误, 因此在此处进行MIME Type检查。
    如果响应是HTML文件,则下一步是将数据传递到渲染器进程,但是如果是zip文件或其他文件,则意味着这是下载请求,因此它们需要将数据传递到下载管理器。

  4. 网络线程从安全站点询问响应数据是否为HTML,并进行安全检查。

    在这个时候,浏览器已经拿到响应了,接下来就开始进行渲染了。
  5. 一旦完成所有检查,并且Network线程确信浏览器应导航到请求的站点,则Network线程将告知UI线程数据已准备就绪。然后,UI线程找到一个渲染器进程来进行网页渲染。

  6. 现在已经准备好数据和渲染器进程,将IPC从浏览器进程发送到渲染器进程以提交导航。它还会传递数据流,因此渲染器进程可以继续接收HTML数据。一旦浏览器进程听到确认已在渲染器进程中进行提交的确认,导航即完成,文档加载阶段开始。
    此时,地址栏已更新,安全指示符和站点设置UI反映了新页面的站点信息。选项卡的会话历史记录将被更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了方便在关闭选项卡或窗口时恢复选项卡/会话,会话历史记录存储在磁盘上。


到这里为止,浏览器的请求和响应就完成了。那在响应之后如何渲染呢,我们接着往下看


渲染

先说几个渲染进程内将要工作的线程:

  • 主线程(Main thread):下载资源、执行js、计算样式、进行布局、绘制合成
  • 光栅线程(Raster thread)
  • 合成线程(Compositor thread)
  • 工作线程(Worker thread)

在下面的渲染过程中,其实就是这四个进程的互相配合,我们一起来看下吧。

  1. 当渲染过程接收提交消息用于导航和开始接收HTML数据,主线程开始解析文本串(HTML),使之成为一个 Document Object Model ,也就是 DOM
  2. 网站有用到图片,CSS 和JavaScript的话,这些东西需要从网络或者缓存中加载,主线程可以边请求,边预加载构建DOM。

  3. 当HTML解析器找到 <script> 标签后,将会暂停HTML解析,并且必须加载、解析和执行 JavaScript的代码。为什么?因为JavaScript 可以使用诸如 document.write() 更改整个DOM结构!所以开发人员在写代码的时候可以在 <script> 标签上加 async 或者 defer 属性。然后浏览器将会异步加载并运行JavaScript,不会阻止解析。
  4. 主线程解析CSS样式,并把CSS样式一一对应到DOM节点上,注意,此时CSS页面还没有生效,只是样式和节点绑定了关系。

  5. 接下来CSS根据DOM节点,会生成类似于DOM结构的一个布局树,仅包含了页面上可见内容的信息,如果有 display: none 等,则该元素不属于布局树。如果有p::before {content:"123"} 等伪类的存在,就算它不在DOM中,也会包含在布局树中。

    在此绘制步骤中,主线程遍历布局树以创建绘制记录。绘画记录是绘画过程的注释,例如“先是背景,然后是文本,然后是矩形”,类似 canvas
    注意:在渲染的时候,每个步骤前面操作的结果都用于创建新数据,如果布局树发生了更改,文档受影响的部分就会重新绘制,也就是 重绘,开发过程中要尽量避免这一现象。
  6. 至此浏览器知道了:文档的结构,每个DOM元素的样式,页面的几何形状以及绘制的顺序。把这些东西换转为屏幕上像素我们称之为 光栅化。在现代浏览器中执行这一行为的过程,称为 合成(Compositing),就是把页面各个部分分成若干层,分别进行栅格化,然后合成器线程的单独线程中进行合成,一个层可以称之为一个 layer。

  7. 层分好了并确定了顺序之后,主线程就把这个信息提交给合成线程,然后合成器线程把每个图层栅格化,发送给栅格线程,栅格线程把它们存储在GPU内存内。

  8. 最终,合成线程将栅格化的块合成帧,并通过IPC传递给浏览器进程,显示在屏幕上。

至此,浏览器的请求,响应和渲染过程结束!

(一半了,稍微休息一下,我们再继续!)


JS单线程

回顾一下,浏览器的渲染进程中,主线程里包括了执行JS,那也就意味着:

JS在浏览器的 渲染进程(Rendered Process) 的 主线程(Main Thread) 内

记住:JS是被设计成单线程的!

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准? —— 阮一峰

所以叙述出来就是:JS逻辑 和 UI渲染 是在一个线程中顺序发生的,二者同一时间只可以存在一个。继续回顾一下上面渲染所提到的,HTML解析器必须等待JS运行,JS是可以操作DOM 和 布局树的,会干扰到主线程在解析HTML的顺序,从而影响结果,所以为了页面的渲染统一,JS被设计成了 执行阻塞UI渲染型。

同时也反映出了一个问题:JS过多会造成页面卡顿,因为走不下去了。所以JS的逻辑一定不能冗余。


任务队列

既然JS是单线程,也就意味着里面的逻辑是排队运行的,后一个任务必须等前一个结束才可以运行。这样就会出一个问题,有没有一种可能是挂起不那么重要的任务,先走重要的,等结束之后再执行挂起的任务呢?

按照这个说法的话,所有任务就可以分成:同步任务(sync)异步任务(async) ,同步任务就是主线程里面的排队进行,异步任务就是不进入主线程,进入一个 “任务队列(task queue)” 的地方呆着,看着主线程里的任务进行,一旦发现主线程的同步任务执行完了,就通知主线程,说我这里的异步任务可以执行了,该任务才会进入主线程执行。

所以有没有发现,那些鼠标点击事件,页面滚动,回调函数,http请求……其实就在任务队列里面。


事件循环(Event Loop)


概念

有了任务队列的存在,就会有事件循环的存在,因为任务队列中可能有很多任务,一个在任务队列的任务进入到主线程后,任务队列依然会看着主线程,看看刚进去的这个有没有执行完毕,毕竟任务队列里还有很多没执行的任务,所以主线程去读取任务队列是循环不断的,也就叫做了 事件循环

这里放张网图,基本上一看就明白了(参考自Philip Roberts的演讲《Help, I'm stuck in an event-loop (opens new window)》)


定时器

这个有点特殊,单独讲一下。

定时器不是个异步事件,是一个定时事件,但是仍属于一个回调操作,是被放在任务队列中的。

就算定时器被设置的时间是0,它也仍然会在主线程逻辑走完之后(此时栈清空了),再执行,所以时间是0的定时器,它可以被理解为希望尽早的执行。

需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。 ——阮一峰


微任务(MicroTask)和宏任务(MacroTask)


这段参考Tasks, microtasks, queues and schedules (opens new window),一位谷歌开发者人员用实例讲述了任务执行顺序,并带有在线Demo,强烈建议过一遍(英语不好就逐句翻译)。

在JS中,主线程的任务叫 宏任务(MacroTask) ,宏任务执行完毕后,立即执行的任务叫 微任务(MicroTask)

宏任务:

  • 主线程已经存在了的任务叫宏任务,从任务队列中进入主线程的任务也叫宏任务,一个宏任务执行过程中,从头到尾不会执行其他的东西
  • 浏览器会在一个宏任务结束后,在下一个宏任务开始前,对页面进行重新渲染

微任务:

  • 当前宏任务执行结束后立即执行的任务叫微任务,也就是说它在前宏任务之后,后宏任务之前,渲染之前!
  • 它的速度比定时器要快,因为不用等待渲染,定时器是宏任务
  • 在一个宏任务执行结束后,所有的微任务都会执行完毕(渲染前)

基于上面的概念,我们可以给常用的任务分下类:

  • 宏任务:主代码,setTimeout,setInterval,setImmediate,requestAnimationFrame,I/O,UI渲染
  • 微任务:Promise,process.nextTick,MutationObserve,queueMicrotask

当然 Vue 中的 nextTick 也就属于微任务了.

目录
相关文章
|
数据采集 人工智能 JavaScript
Browser Use:40.7K Star!一句话让AI完全接管浏览器!自动规划完成任务,多标签页同时管理
Browser Use 是一款专为大语言模型设计的智能浏览器自动化工具,支持多标签页管理、视觉识别、内容提取等功能,并能记录和重复执行特定动作,适用于多种应用场景。
3951 21
Browser Use:40.7K Star!一句话让AI完全接管浏览器!自动规划完成任务,多标签页同时管理
|
人工智能 开发框架 自然语言处理
Eko:一句话就能快速构建复杂工作流的 AI 代理开发框架!快速实现自动操作电脑和浏览器完成任务
Eko 是 Fellou AI 推出的开源 AI 代理开发框架,支持自然语言驱动,帮助开发者快速构建从简单指令到复杂工作流的智能代理。
2187 12
Eko:一句话就能快速构建复杂工作流的 AI 代理开发框架!快速实现自动操作电脑和浏览器完成任务
|
JavaScript 前端开发
Node.js 中实现多任务下载的并发控制策略
Node.js 中实现多任务下载的并发控制策略
662 15
|
数据采集 消息中间件 JavaScript
浏览器渲染揭秘:从加载到显示的全过程;浏览器工作原理与详细流程
了解浏览器工作原理与流程,能有效帮助前端开发与性能优化。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
Web App开发 人工智能 JavaScript
Nanobrowser:开源版OpenAI Operator!AI自动操控浏览器,复杂网页任务一键搞定
Nanobrowser 是一款开源的 Chrome 扩展工具,基于多智能体系统实现复杂的网页任务自动化,支持多种大型语言模型,完全免费且注重隐私保护。
2549 1
|
存储 人工智能 数据库
Codel:AI代理工具,支持在终端、浏览器、编辑器执行复杂任务和项目
Codel是一款全自主AI代理工具,支持在终端、浏览器和编辑器中执行复杂任务和项目。它运行在沙盒化的Docker环境中,具备自主操作能力,内置浏览器和文本编辑器,所有操作记录存储于PostgreSQL数据库。Codel能够自动完成复杂任务,如创建项目结构、进行网络搜索等,适用于自动化编程、研究与开发、教育与培训以及数据科学与分析等多个领域。
733 11
Codel:AI代理工具,支持在终端、浏览器、编辑器执行复杂任务和项目
|
人工智能 API 数据库
Browser Use:开源 AI 浏览器助手,自动完成网页交互任务,支持多标签页管理、视觉识别和内容提取等功能
Browser Use 是一款专为大语言模型设计的智能浏览器工具,支持多标签页管理、视觉识别、内容提取等功能,并能记录和重复执行特定动作,适用于多种应用场景。
4199 0
Browser Use:开源 AI 浏览器助手,自动完成网页交互任务,支持多标签页管理、视觉识别和内容提取等功能
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
JavaScript 前端开发 API
浏览器渲染过程中如何处理异步任务
在浏览器渲染过程中,异步任务通过事件循环机制处理。JS执行时,同步任务在主线程上执行,形成一个执行栈。异步任务则被推入任务队列中,待主线程空闲时按顺序调用,确保页面流畅渲染与响应。