在回答这个问题之前,我们需要先了解下浏览器的架构。
chrome架构
目前Chrome采用的是多进程的架构模式,可分为主要的五类进程,分别是:浏览器(Browser)主进程、 GPU(Graphics Processing Unit的首字母缩写,即图形处理单元) 进程、网络(NetWork)进程、多个渲染进程和多个插件进程;
- 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎Blink和JavaScript引擎V8都是运行在该进程中,默认情况下,Chrome会为每个Tab标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU进程。其实,Chrome刚开始发布的时候是没有GPU进程的。而GPU的使用初衷是为了实现3D CSS的效果,只是随后网页、Chrome的UI界面都选择采用GPU来绘制,这使得GPU成为浏览器普遍的需求。最后,Chrome在其多进程架构上也引入了GPU进程。
- 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
在开始之前,我们一起看下,Chrome 打开一个页面需要启动多少进程?你可以点击 Chrome 浏览器右上角的“选项”菜单,选择“更多工具”子菜单,点击“任务管理器”,这将打开 Chrome 的任务管理器的窗口,如下图:
打开 1 个页面至少需要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程,共 4 个;如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。
输入url
1.用户输入url --- 浏览器主进程
- 1.组装协议:浏览器检查url,组装协议,构成完整的url
- 2.浏览器进程通过进程间通信(IPC)把url请求发送给网络进程;
2.发送请求 --- 网络进程
- 1.网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程;如果没有则发送请求
- 2.DNS解析,获取服务器ip地址,建立TCP链接
- 3.发送http请求
- 4.服务器端处理请求;
DNS解析:参考 juejin.cn/post/704932…
TCP三次握手四次挥手: 参考 juejin.cn/post/704933…
3.准备渲染进程
- 1.浏览器进程检查当前url是否与之前打开了渲染进程的页面的根域名相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程;
4.渲染进程
- 1.构建DOM树:先将请求回来的数据解压,随后HTML解析器将其中的HTML字节流通过分词器拆分为一个个Token,然后生成节点Node,最后解析成浏览器识别的DOM树结构。
- 2.CSS解析器将CSS转换为浏览器能识别的styleSheets也就是CSSOM:可以通过控制台输入document.styleSheets查看
这里要考虑一下阻塞的问题,由于JavaScript有修改CSS和HTML的能力,所以,需要先等到 CSS 文件下载完成并生成 CSSOM,然后再执行 JavaScript 脚本,最后再继续构建 DOM。由于这种阻塞,导致了解析白屏;
优化方案:
- 移除js和css的文件下载:通过内联 JavaScript、内联 CSS;
- 尽量减少文件大小:如通过 webpack 等工具移除不必要的注释,并压缩 js 文件;
- 将不进行DOM操作或CSS样式修改的 JavaScript 标记上 sync 或者 defer异步引入;
- 使用媒体查询属性:将大的CSS文件拆分成多个不同用途的 CSS 文件,只有在特定的场景下才会加载特定的 CSS 文件。
- 3.样式计算
- 1.转换样式表中的属性值,使其标准化。比如将em转换为px,color转换为rgb;
- 2.计算DOM树中每个节点的具体样式,这里遵循CSS的继承和层叠规则;可以通过Chrome调试工具的Elements选项的Computed查看某一标签的最终样式;
- 4.布局阶段
- 1.创建布局树,遍历DOM树中的所有节点,去掉所有隐藏的节点(比如head,添加了display:none的节点),只在布局树中保留可见的节点
- 2.计算布局树中节点的坐标位置(较复杂,这里不展开);
- 5.分层
- 1.对布局树进行分层,并生成分层树(Layer Tree),可以通过Chrome调试工具的Layer选项查看。分层树中每一个节点都直接或间接的属于一个图层(如果一个节点没有对应的层,那么这个节点就从属于父节点的图层)
- 6.涂层绘制
1.为每个图层生成绘制列表(即绘制指令),并将其提交到合成线程。以上操作都是在渲染进程中的主线程中进行的,提交到合成线程后就不阻塞主线程了;
- 7.切分图块
- 1.合成线程将图层切分成大小固定的图块(256x256或者512x512)然后优先绘制靠近视口的图块,这样就可以大大加速页面的显示速度
5.GPU进程
- 1.栅格化操作
-
1.在光栅化线程池中将图块转换成位图,通常这个过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。
6.浏览器主进程
- 合成与显示
- 1.合成:一旦所有图块都被光栅化,合成线程就会将它们合成为一张图片,并生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程
- 2.显示:浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。
总结
1.渲染进程将 HTML 内容转换为能够读懂DOM 树结构。
2.渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。
3.创建布局树,并计算元素的布局信息。
4.对布局树进行分层,并生成分层树。
5.为每个图层生成绘制列表,并将其提交到合成线程。 合成线程将图层分图块,并栅格化将图块转换成位图。
6.合成线程发送绘制图块命令给浏览器进程。浏览器进程根据指令生成页面,并显示到显示器上。
参考: