900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 前端开发这么多年 你真的了解浏览器页面渲染机制吗? | 技术头条

前端开发这么多年 你真的了解浏览器页面渲染机制吗? | 技术头条

时间:2020-05-15 17:28:33

相关推荐

前端开发这么多年 你真的了解浏览器页面渲染机制吗? | 技术头条

作者 |浪里行舟

前言

浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另一个是JS引擎。渲染引擎在不同的浏览器中也不是都相同的。目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。这里面大家最耳熟能详的可能就是Webkit内核了,Webkit内核是当下浏览器世界真正的霸主。本文我们就以Webkit为例,对现代浏览器的渲染过程进行一个深度的剖析。

页面加载过程

在介绍浏览器渲染过程之前,我们简明扼要介绍下页面的加载过程,有助于更好理解后续渲染过程。

要点如下:

浏览器根据 DNS 服务器得到域名的 IP 地址

向这个 IP 的机器发送 HTTP 请求

服务器收到、处理并返回 HTTP 请求

浏览器得到返回内容

例如在浏览器输入https://juejin.im/timeline,然后经过DNS解析,juejin.im对应的IP是36.248.217.149(不同时间、地点对应的IP可能会不同)。然后浏览器向该IP发送HTTP请求。

服务端接收到HTTP请求,然后经过计算(向不同的用户推送不同的内容),返回HTTP请求,返回的内容如下:

其实就是一堆HMTL格式的字符串,因为只有HTML格式浏览器才能正确解析,这是W3C标准的要求。接下来就是浏览器的渲染过程。

浏览器渲染过程

浏览器渲染过程大体分为如下三部分:

1)浏览器会解析三个东西:

一是HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。

二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像。

三是Javascript脚本,等到Javascript脚本文件加载后,通过DOMAPI和CSSOMAPI来操作DOMTree和CSSRuleTree。

2)解析完成后,浏览器引擎会通过DOMTree和CSSRuleTree来构造RenderingTree。

RenderingTree渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息。

CSS的RuleTree主要是为了完成匹配并把CSSRule附加上RenderingTree上的每个Element(也就是每个Frame)。

然后,计算每个Frame的位置,这又叫layout和reflow过程。

3)最后通过调用操作系统NativeGUI的API绘制。

接下来我们针对这其中所经历的重要步骤详细阐述。

构建DOM

浏览器会遵守一套步骤将HTML文件转换为DOM树。宏观上,可以分为几个步骤:

浏览器从磁盘或网络读取HTML的原始字节,并根据文件的指定编码(例如UTF-8)将它们转换成字符串。

在网络中传输的内容其实都是0和1这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。

将字符串转换成Token,例如:<html>、<body>等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息。

这时候你一定会有疑问,节点与节点之间的关系如何维护?

事实上,这就是Token要标识“起始标签”和“结束标签”等标识的作用。例如“title”Token的起始标签和结束标签之间的节点肯定是属于“head”的子节点

上图给出了节点之间的关系,例如:“Hello”Token位于“title”开始标签与“title”结束标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。

生成节点对象并构建DOM

事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象。注意:带有结束标签标识的Token不会创建节点对象。

接下来我们举个例子,假设有段HTML文本:

<html><head><title>Webpageparsing</title></head><body><div><h1>Webpageparsing</h1><p>ThisisanexampleWebpage.</p></div></body></html>

上面这段HTML会解析成这样:

构建CSSOM

DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。

构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM

在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归CSSOM树,然后确定具体的元素到底是什么样式。

注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。

构建渲染树

当我们生成DOM树和CSSOM树以后,就需要将这两棵树组合为渲染树。

在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是display:none的,那么就不会在渲染树中显示。

我们或许有个疑惑:浏览器如果渲染过程中遇到JS文件怎么处理?

渲染过程中,如果遇到<script>就停止渲染,执行JS代码。因为浏览器渲染和JS执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染DOM冲突。JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。

也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载JS文件,这也是都建议将script标签放在body标签底部的原因。当然在当下,并不是说script标签必须放在底部,因为你可以给script标签添加defer或者async属性(下文会介绍这两者的区别)。

JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。

原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。

这是什么情况?

这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。

布局与绘制

当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。

布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。

布局完成后,浏览器会立即发出“PaintSetup”和“Paint”事件,将渲染树转换成屏幕上的像素。

以上我们详细介绍了浏览器工作流程中的重要步骤,接下来我们讨论几个相关的问题:

几点补充说明

1.async和defer的作用是什么?有什么区别?

接下来我们对比下defer和async属性的区别:

其中蓝色线代表JavaScript加载;红色线代表JavaScript执行;绿色线代表HTML解析。

1)情况1<scriptsrc="script.js"></script>

没有defer或async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

2)情况2<scriptasyncsrc="script.js"></script>(异步下载)

async属性表示异步执行引入的JavaScript,与defer的区别在于,如果已经加载好,就会开始执行——无论此刻是HTML解析阶段还是DOMContentLoaded触发之后。需要注意的是,这种方式加载的JavaScript依然会阻塞load事件。换句话说,async-script可能在DOMContentLoaded触发之前或之后执行,但一定在load触发之前执行。

3)情况3<scriptdefersrc="script.js"></script>(延迟执行)

defer属性表示延迟执行引入的JavaScript,即这段JavaScript加载时HTML并未停止解析,这两个过程是并行的。整个document解析完毕且defer-script也加载完成之后(这两件事情的顺序无关),会执行所有由defer-script加载的JavaScript代码,然后触发DOMContentLoaded事件。

defer与相比普通script,有两点区别:载入JavaScript文件时不阻塞HTML的解析,执行阶段被放到HTML标签解析完成之后;在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

2.为什么操作DOM慢?

把DOM和JavaScript各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能JavaScript》

JS是很快的,在JS中修改DOM对象也是很快的。在JS的世界里,一切是简单的、迅速的。但DOM操作并非JS一个人的独舞,而是两个模块之间的协作。

因为DOM是属于渲染引擎中的东西,而JS又是JS引擎中的东西。当我们用JS去操作DOM时,本质上是JS引擎和渲染引擎之间进行了“跨界交流”。这个“跨界交流”的实现并不简单,它依赖了桥接接口作为“桥梁”(如下图)。

过“桥”要收费——这个开销本身就是不可忽略的。我们每操作一次DOM(不管是为了修改还是仅仅为了访问其值),都要过一次“桥”。过“桥”的次数一多,就会产生比较明显的性能问题。因此“减少DOM操作”的建议,并非空穴来风。

3.你真的了解回流和重绘吗

渲染的流程基本上是这样(如下图黄色的四个步骤):1.计算CSS样式2.构建RenderTree3.Layout–定位坐标和大小4.正式开画

注意:上图流程中有很多连接线,这表示了Javascript动态修改了DOM属性或是CSS属性会导致重新Layout,但有些改变不会重新Layout,就是上图中那些指到天上的箭头,比如修改后的CSSrule没有被匹配到元素。

这里重要要说两个概念,一个是Reflow,另一个是Repaint

重绘:当我们对DOM的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。

回流:当我们对DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)

我们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复回流+重绘或者只有重绘。回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。

1)常见引起回流属性和方法

任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流,

添加或者删除可见的DOM元素;

元素尺寸改变——边距、填充、边框、宽度和高度

内容变化,比如用户在input框中输入文字

浏览器窗口尺寸改变——resize事件发生时

计算 offsetWidth 和 offsetHeight 属性

设置 style 属性的值

2)常见引起重绘属性和方法

3)如何减少回流、重绘

使用 transform 替代 top

使用visibility替换display:none,因为前者只会引起重绘,后者会引发回流(改变了布局)

不要把节点的属性值放在一个循环里当成循环里的变量。

for(leti=0;i<1000;i++){//获取offsetTop会导致回流,因为需要去获取正确的值console.log(document.querySelector('.test').style.offsetTop)}

不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame

CSS 选择符从右往左匹配查找,避免节点层级过多

将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于video标签来说,浏览器会自动将该节点变为图层。

性能优化策略

基于上面介绍的浏览器渲染原理,DOM和CSSOM结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。

JS优化:<script>标签加上defer属性和async属性用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。defer属性:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。async属性:HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。

CSS优化:<link>标签的rel属性中的属性值设置为preload能够让你在你的HTML页面中可以指明哪些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能

总结

综上所述,我们得出这样的结论:

浏览器工作流程:构建DOM->构建CSSOM->构建渲染树->布局->绘制。

CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。

通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建。

参考文章

async 和 defer 的区别 | SegmentFault

浏览器的渲染原理简介

前端面试之道

关键渲染路径

前端性能优化原理与实践

由入门到专家:前端全链路开发实践手册

Web 前端面试指南与高频考题解析

作者:浪里行舟,硕士研究生,专注于前端,运营有个人公众号前端工匠,致力于打造适合初中级工程师能够快速吸收的一系列优质文章。

声明:本文为作者投稿,版权归对方所有,如需转载请联系原作者。

如何挑战百万年薪的人工智能!

/topic/ai30?utm_source=csdn_bw

【End】

☞谁说国产操作系统没救了?|人物志

☞Google+为什么会死?

☞华米OV一加“征服”印度?!

☞程序员 996 再上热搜,黑名单增至84家!

☞与云原生及开源大神们的第二次亲密接触 | 全议程重磅发布

☞V神玩起freestyle!5位以太坊核心大咖在悉尼的演讲精华全在这了!|直击EDCON

☞“重构”黑洞:26岁MIT研究生的新算法|人物志

☞程序员为什么都爱穿冲锋衣?(最全总结)

System.out.println("点个在看吧!");

console.log("点个在看吧!");

print("点个在看吧!");

printf("点个在看吧!\n");

cout<<"点个在看吧!"<<endl;

Console.WriteLine("点个在看吧!");

Response.Write("点个在看吧!");

alert("点个在看吧!")

echo "点个在看吧!"

点击阅读原文,输入关键词,即可搜索您想要的CSDN文章。

你点的每个“在看”,我都认真当成了喜欢

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。