万字好文带你了解浏览器原理

背景

为什么要了解浏览器原理?

当面试官问你输入url到渲染发生了什么这种问题你不知所措?

页面中到底能承载多少个元素,取决于什么条件?如果一个页面在2s内打不开,你应该如何优化?

DOM是什么,javascript操作的是DOM还是html?

回流和重绘又是什么?浏览器架构是什么样的?

当你能够细化的了解整个了浏览器工作原理的时候,你就能很好的处理这些问题

到底什么是浏览器

浏览器我们常用的有谷歌 IE Safari 火狐等等,目前开发者心中的浏览器只有一个,那就是谷歌浏览器,它的市场份额稳居第一,从未被超越

以工厂为例,我们可以把工厂理解成进程,工人理解成线程,工人只能在工厂工作,一个工厂可以有很多工人,同一个工厂内的工人很容易交流,不同的工厂内的工人不容易交流,只是会比较费劲,一个工厂不会影响另一个工厂,但是工厂内的工人会影响这个工厂的运行

当启动一个应用程序时,会创建一个进程或者多个进程,该程序可能会创建线程来帮助它工作。操作系统为每个进程提供了可用的内存,所有应用程序状态都保存在内存空间中。当您关闭应用程序时,程序创建的进程也会消失,占有的内存也会被释放

chrome架构

了解了进程和线程的关系之后,我们可以看一下启动chrome浏览器需要占用多少进程

多进程架构

谷歌浏览器自带了一个任务管理器,点击浏览器【更多工具】→【任务管理器】

站点隔离并不是我们想象的这么简单,它改变了iframe和页面的交互方式,即便是多个渲染进程,当你打开devtools的时候,它们看起来还是那么完美,并且当你用ctrl+f对页面进行搜索的时候,多个渲染进程之间的搜索工程师们也优化的你看不出丝毫破绽

了解了浏览器的架构,来讲一个面试官最喜欢问的面试题,当浏览器输入url之后发生了什么?

输入url之后发生了什么

我们使用浏览器的主要目的就是为了搜索或者访问某些网站,就让我们从浏览器的角度,来看看我们是如何进行搜索或者网站的访问的

一个完整的URL如下所示

这张图其实画的有点多,但是主要是想让大家了解在获取资源之前,其实是有一个缓存的判断的,否则就不会发送对应的请求

建立连接

建立连接的过程就是三次握手的过程,表示整个过程要发送三次包

这个时候需要浏览器进程跟渲染进程通过IPC进行通信,通信过程还需要传递数据流,方便渲染进程可以持续接收html数据,一旦渲染进程渲染完毕,便会通知浏览器进程当前完毕,导航阶段就完成了,就开始了加载文档的过程

这个时候,地址栏更新。安全指示器和站点设置的UI反应站点的信息,选项卡的历史记录会被更新,前进后退等历史记录逐步被更新,历史记录同样也会在磁盘上存储一份,方便进行整个历史浏览的检索

service worker独立于当前网页的线程,所以执行大量的操作也不会阻塞主线程

渲染进程如何工作

上面的过程把html文件已经交给了渲染进程,渲染进程负责页签的显示,在一个渲染进程中,主线程负责解析,编译代码,运行等工作,它的核心就是将HTML、CSS和JavaScript转换成用户可以与之交互的网页

当然渲染进程是一个多线程架构,它主要有以下线程:GUI线程、JavaScript引擎线程、定时器触发线程、事件触发线程、http请求线程、合成线程和IO线程

GUI渲染线程

拿到数据之后,GUI渲染线程就开始解析HTML并将其转换成DOM(文档对象模型),DOM是浏览器对页面的内部表示,javascript获取和操作的页面元素本质是浏览器提供的DOM数据,同时当页面发生重绘和回流的时候,该线程也会执行

在解析过程中,即便是你的html语法有一些异常,比如没有关闭标签,匹配错误等,浏览器也不会抛出异常,比如如下代码,在浏览器上会自动解析成功

<body>
  <div>
  </p>
</body>
image.png

这里还需要注意一点的是,GUI渲染线程和JavaScript引擎线程是互斥的,当JavaScript引擎线程执行的时候,GUI线程是被挂起的,相当于是冻结状态,GUI的更新会被保存在一个队列中等JavaScript引擎空闲的时候立刻执行。之所以这样是因为JS代码可能会改变DOM结构,所以JavaScript引擎执行时间过长是会阻塞页面的渲染的,了解这一点也就知道为什么fiber架构为什么能够让大型应用看起来不卡顿

在解析html的过程中,其实还有一些其它的资源,比如img或者link,这个时候就会给浏览器进程的网络线程发送信息,GUI线程会根据这些额外的资源是否会阻塞转换过程而决定是不是需要资源加载完毕。比如碰到script标签有可能就会阻塞,但是也有例外,script标签添加了async或者defer属性

JavaScript引擎线程

负责解析JavaScript脚本,运行代码

事件触发线程

比如我们的点击事件,滚动事件,异步请求,或者执行setTimeout等这些事件时,会将对应的任务添加到事件触发线程,当这个事件被触发的时候,则把触发的事件回调添加到待处理队列的队尾。由于javascript是单线程的,所以处理这些事件都必须排队

定时器线程

setInterval与setTimeout所在线程,计数通过上面的内容,可以得到不可能通过javascript线程计数,否则会阻塞,因此会有一个单独的线程进行计数的处理,等待时间达到后,将回调函数添加到事件队列

HTTP请求线程

在XMLHttpRequest连接后是通过浏览器新开一个线程请求,监测到获取了对应的内容后,将回调函数添加到事件队列,再由javascript引擎执行

合成线程后面会讲,IO线程主要用于和其它的线程通信

布局

当前DOM已经有了,但是精美的页面光有DOM是不够的,只有DOM是不会出现我们的五彩斑斓的页面,需要CSS让页面变得更美观,GUI线程会解析CSS并决定每个DOM元素的样式

image.png

如果你没有设置对应的样式,浏览器也有自己的内置的一些标签样式,比如h1-h6

有了样式,渲染进程已经知道了每个结点呈现的效果,但是节点的位置信息怎么来,这个时候需要布局树,渲染进程会遍历DOM结构(包含样式),布局树只包含在页面中显示的元素,当一个元素被设置为display: none的时候布局树中是没有这个元素的。同理如果div::before { content: 'Hi!' },则布局树中是存在这个Hi的,DOM树javascript能够获取,但是布局树获取不到

布局树的描述非常具有挑战性,因为你需要对整个页面进行精确的描绘。布局中存在浮动、定位、固定、文字换行,自动伸缩,各种元素的结合,可以想象这个任务多么繁重

绘制

我们有了布局树的信息之后是不是就能绘制了,其实并不是,虽然你知道了每个元素的位置,但是它们绘制的顺序是怎样的其实还是不清楚,到底哪个元素先,哪个元素后,了解PS的同学,肯定知道图层的概念,哪个元素应该在哪个元素的顶部?CSS有控制元素层级的一个属性,叫做z-index,用过的同学应该都了解

image.png

这个阶段会通过布局树形成绘制记录,绘制记录本质就是绘制的一系列步骤,比如我要先干什么,在干什么(先绘制背景,再绘制元素内容,再绘制形状等等)

渲染

渲染的过程开销是很大的,任何一个小的变化都会引起一系列的变化,当布局树发生变化的时候,绘制需要重新构建页面变化,页面有动画的效果的时候,每一帧都需要更新动画内容,如果无法保证帧动画,给用户感官上就会出现卡顿

image.png
image.png
image.png

javascript也会阻塞页面的渲染,导致卡顿的发生,可以将 Javascript 操作优化成小块,然后使用requestAnimationFrame()

  • 重排:表示布局树发生变化的时候,整个布局树要重新构建,形成绘制记录,重新回炉修炼
  • 重绘:不影响元素位置信息的,比如元素的颜色发生变化,但是元素位置未发生改变,只需要重绘合成目前我们已经有了所有的信息,文档结构-元素样式-元素几何-布局树-绘制记录,最终将绘制记录转换到屏幕上的像素称之为光栅化

之前的方式是可视区域进行光栅化,滚动的时候再次进行光栅化,如下所示

AiIny83Lk4rTzsM8bxSn.gif

但是现在浏览器有着更好的处理方式,这个方式被叫做合成

合成会将一个页面拆成很多层,每个层在不同的的合成线程中进行光栅化,然后组合成一个新的页面。滚动过程中如果这个层已经光栅化,则使用已经光栅化的层进行合成

Aggd8YLFPckZrBjEj74H.gif

那这个时候问题就来了,一个层中要包含哪些元素呢?主线程需要遍历布局树,做为开发者,想要创建一个新的层,可以使用css 属性will-change让浏览器创建层

光栅化各个层之后,将其存储在GPU的缓存中,合成线程也能够决定相应的优先级,保证用户看到的部分最先被光栅化,每当有交互发生变化,合成线程就会创建更多的合成帧然后通过 GPU 将新的部分渲染出来

浏览器的事件体系

事件是什么?比如按钮的点击,input输入框的内容输入,鼠标滚轮和拖拽,都是事件

交互的时候浏览器进程最先接收到事件,浏览器关心的只有当前事件发生在哪个页签,然后将事件位置信息和事件类型发送到当前页签的渲染进程,渲染进程会找到事件发生的元素和对应的事件

image.png

但是前面也说到,页面是被光栅化的,在合成线程处理页面的时候,合成线程会标记有事件监听的区域,有这些信息,合成线程就会将触发的事件发送给主线程处理

总结

浏览器的多进程架构,根据不同的功能划分了不同的进程,进程内不同的使命划分了不同的线程,当用户开始浏览网页时候,浏览器进程进行处理输入、开始导航请求数据、请求响应数据,查找新建渲染进程,提交导航,之后渲染又进行了解析HTML构建DOM、构建过程加载子资源、下载并执行JS代码、样式计算、布局、绘制、合成,一步一步的构建出一个可交互的WEB页面,浏览器进程又接受页面的交互事件信息,并将其交给渲染进程,渲染进程内主进程进行命中测试,查找目标元素并执行绑定的事件,完成页面的交互。

刚踏上开发之路时,我几乎只关注怎样去写代码、怎样提升自己的生产效率。诚然,这些事情很重要,但与此同时我们也应当思考浏览器会怎么去处理我们书写的代码。现代浏览器一直致力探索如何提供更好的用户体验。书写对浏览器友好的代码,反过来也能提供友好的用户体验。希望能够通过这节课让大家了解浏览器的运行机制和原理,构建出对浏览器更为友好的代码。同时也能够不断优化我们的业务,让用户体验更上一层楼,这就是本节课全部内容

最后,感谢大家阅读,码字不易,一键三连

版权声明:
作者:FE情报局
链接:https://jkboy.com/archives/8311.html
来源:随风的博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
万字好文带你了解浏览器原理
页面中到底能承载多少个元素,取决于什么条件?如果一个页面在2s内打不开,你应该如何优化?
<<上一篇
下一篇>>