内容来自浏览器的工作原理 听完大佬的讲解,自己整理了一下视频的内容
简化的浏览器结构图
用户界面:用于展示除标签页窗口之外的其他用户界面内容 渲染引擎:负责渲染用户请求的页面内容,浏览器的核心,往往把渲染引擎称为浏览器的内核 浏览器引擎:用于在用户界面和渲染引擎之间传递数据
渲染器下面还有很多小功能模块 比如负责网络请求的网络模块 用于解析和执行js的js解释器 数据持久层:帮助浏览器存储各种数据 例如cookie
浏览器的多进程结构
现代浏览器都是多进程结构: 浏览器进程:负责控制除标签页窗口之外的用户界面,包括地址栏,书签,前进 后退按钮等,以及负责与浏览器的其他进程协调工作 网络进程:负责发起网络请求 GPU进程:负责整个页面的展示和渲染 插件进程:负责控制网站使用的所有插件,例如flash,并不是指浏览器安装的扩展 渲染器进程用来控制显示tab标签内的所有内容,浏览器默认情况下会为每个标签页创建一个渲染器进程(和Chrome启动时选择的进程模型有关)
请求数据并且渲染数据到页面
当我们在地址栏输入内容时,浏览器进程的UI线程会捕捉该内容,如果输入的是网址,则UI线程会启动一个网络线程来请求DNS进行域名解析,接着开始连接服务器开始获取数据,如果你的输入不是网址,而是一串关键字,浏览器就会使用默认配置的搜索引擎来查询。 网络线程获取到的数据是如何显示在页面上的呢? 当网络线程获取到数据后,会通过SafeBrowsing来检查站点是否是恶意站点,如果是则会展示警告页面,告诉我们这个站点可能会有问题,safeBrowsing谷歌的一套站点安全检测系统,通过检测该站点的数据,比如通过判断该站点的IP是否在谷歌的黑名单之内 当返回数据检测完毕,并且安全通过校验时,网络线程就会通知UI线程数据已经准备好了,并将数据传递给浏览器进程,然后UI线程会创建一个渲染器进程来渲染页面 此时浏览器进程通过IPC管道将数据传递给渲染器进程,下面就是正式进入渲染流程,渲染器接受到的数据就是HTMl,它的核心任务就是将html中js,src,image,css等资源渲染成用户可以交互的页面,
渲染流程
1.构造DOM Tree
渲染器进程的主线程将HTML解析,构造DOM Tree数据结构(DOM文档对象模型,由多层节点构成的文档,用户可以通过他用户可以修改,删除,增加页面的内容) Html首先经过Tokeniser标记化,通过词法分析将输入的html内容解析成多个标记,根据识别后的标记进而构造DOM树,在DOM树创建过程中会构造document对象,然后对以document对象为根节点的DOM树的节点不断进行修改,向其中添加各种元素,Html中会有一些额外的资源,比如css,js脚本等,图片等,图片和css这些资源需要通过网络下载或从缓存中直接加载,这些加载不会阻塞html的解析,因为他们不会影响DOM树的生成,但当HTMl解析过程中遇到script标签,会停止解析HTML流程,转而去加载解析并且执行js,为什么浏览器不会等待把HTML解析完成后,在去加载和解析js呢,这是因为浏览器不知道js的执行是否会影响到当前页面的HTML结构 ,如果js代码里用到了document.write方法来修改HTML,那之前的HTML解析就没有任何意义了,这也就是为什么说要把script标签放到合适的位置,或者使用async或defer属性来异步加载js,在html完成后,我们就会获得一个DOM Tree 2.构造Layout Tree
获得了DOM Tree之后,但我们还不知道每个节点长什么样子,主线程会解析css,来确定每个DOM节点的样式,浏览器中会有默认的样式,比如H2的字体要比H3的字体大,在知道了每个节点以及对应的样式,接下来计算节点的位置,也就是节电的坐标以及该节点需要占用多大的区域,这个阶段被称为Layout布局,主线程通过遍历DOM tree和计算的样式来生成Layout Tree,Layout Tree上的每个节点都计算好了x,y坐标和边框尺寸,这里需要注意的是DOM Tree和Layout Tree上的节点不是一一对应的,设置了display:none的节点不会出现在Layout Tree上,而在before伪类中添加了content值的元素,content里的内容会出现在Layout Tree上,不会出现在DOM Tree中,这是因为DOM Tree是解析HTML生成的,和样式没有关系,而Layout Tree是遍历DOM Tree和计算好的样式来生成的,Layout Tree和最终显示在屏幕上的节点相对应的,现在我们已经知道了DOM 节点的大小形状和位置 3.生成Layer Tree和绘制记录表(Paint Record)
知道了DOM 节点的大小形状和位置,我们还需要知道以什么样的顺序来绘制这样的节点,举例来说,z-index属性会影响节点绘制的层级关系,如果我们按照DOM Tree的顺序来绘制节点,则会导致错误的渲染,所以为了保证能够在屏幕上展示正确的层级,主线程会遍历Layout Tree,通过遍历Layout Tree,生成Layer Tree,同时生成一个绘制记录表(Paint Record)该表记录了绘制的顺序,这个阶段被称为绘制 4.栅格化 下一步就是把这些信息转化成像素点,显示在屏幕上,这个行为被称为栅格化,(Rastering) 早期的栅格化 只栅格化展示在用户可视区域的内容,当用户滚动时,在栅格化更多的内容来补充缺失的部分,这种方式会到导致延迟
现在Chrome采用的栅格化技术,叫做合成(Composting)
将页面的多个部分分成多个图层 ,分别对齐进行栅格化,并在合成器线程中(Compositer Thread)单独 进行合成页面的技术,简单来说,就是页面所有的元素按照某种规则进行分图层, 并把图层都栅格化好了, 然后把可视区域中的内容展示成一帧展示给用户即可, 主线程解析生成Layout Tree 生成Layer Tree ,并且当绘制顺序确定好以后,主线程将这些信息传递给合成器线程 合成器线程将每个图层栅格化,由于一层可能像整个页面的长度那样大,因此合成器线程将他们切分成很多图块,然后将每个图块发送给栅格化线程,栅格线程栅格化每个图块,并将他们存储在GPU中 当图块栅格化完成后,合成器线程将收集称为“Draw quads” 这些信息记录了图块在内存中的位置和在页面的那个位置绘制图块的信息, 根据这些信息合成器线程生成一个合成器帧 然后这个合成器帧通过IPC发送给浏览器进程,接着浏览器进程将合成器帧传送到GPU,然后GPU渲染展示到屏幕上,这时候我们就可以看到页面的内容了,当页面发生变化,比如滚动页面,就会生成新的合成器帧,然后传送给浏览器进程,进而传送到GPU进行渲染
大致流程
浏览器进程中的网络线程获取到HTML数据之后,交给渲染器主线程进行解析,生成DOM Tree,接着,进行样式计算,主线程通过遍历DOM Tree和计算好的样式生成Layout Tree, 通过遍历Layout Tree生成Layer Tree和绘制信息表, 然后渲染器主线程将Layer Tree和Paint一起传送给合成器线程, 合成器线程按照规则将Layer Tree分层,并没每一层进行分块,将分好的块传送给栅格化线程进行栅格化,栅格化线程将栅格完成之后的draw quads图块信息发送给合成器线程,合成器线程将其合成一帧,然后将合成器帧通过IPC传回给浏览器进程,浏览器进程在传送给GPU进行渲染,之后就展示到屏幕上了 当我们改变一个元素的尺寸位置属性时,会重新进行样式计算, 布局(Layout)和绘制(Paint)以及后面的所有流程,这种情况称之为重排 当我们改变一个元素的颜色属性时,不会触发布局重排,但是会触发样式计算和绘制, 这个就是重绘 重绘和重排都会占用渲染器进程的主线程,我们,js 代码也是运行在主线程上的,这时就会出现抢占线程的问题,如果写了一个大量重绘或者重排的动画,而此时js在主线程的运行会导致页面出现卡顿的效果。不过我们可以尽量使用css中的transform,CSStransform属性允许你旋转,缩放,倾斜或平移给定元素,通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格化线程
|