对于前端来说,HTML 都是最基础的内容。
今天,我们来了解一下 HTML 和网页有什么关系,以及与 DOM 有什么不同。通过本讲内容,你将掌握浏览器是怎么处理 HTML 内容的,以及在这个过程中我们可以进行怎样的处理来提升网页的性能,从而提升用户的体验。
不知你是否有过这样的体验:当打开某个浏览器的时候,发现一直在转圈,或者等了好长时间才打开页面……
此时的你,会选择关掉页面还是耐心等待呢?
这一现象,除了网络不稳定、网速过慢等原因,大多数都是由于页面设计不合理导致加载时间过长导致的。
我们都知道,页面是用 HTML/CSS/JavaScript 来编写的。
HTML由一系列的元素组成,通常称为HTML元素。HTML 元素通常被用来定义一个网页结构,基本上所有网页都是这样的 HTML 结构:
<html>
<head></head>
<body></body>
</html>
其中:
HTML 中的元素特别多,其中还包括可用于 Web Components 的自定义元素。
前面我们提到页面 HTML 结构不合理可能会导致页面响应慢,这个过程很多时候体现在<script>和<style>元素的设计上,它们会影响页面加载过程中对 Javascript 和 CSS 代码的处理。
因此,如果想要提升页面的加载速度,就需要了解浏览器页面的加载过程是怎样的,从根本上来解决问题。
浏览器在加载页面的时候会用到 GUI 渲染线程和 JavaScript 引擎线程(更详细的浏览器加载和渲染机制将在第 7 讲中介绍)。其中,GUI 渲染线程负责渲染浏览器界面 HTML 元素,JavaScript 引擎线程主要负责处理 JavaScript 脚本程序。
由于 JavaScript 在执行过程中还可能会改动界面结构和样式,因此它们之间被设计为互斥的关系。也就是说,当 JavaScript 引擎执行时,GUI 线程会被挂起。
以网易云课堂官网为例,我们来看看网页加载流程。
(1)当我们打开官网的时候,浏览器会从服务器中获取到 HTML 内容。
(2)浏览器获取到 HTML 内容后,就开始从上到下解析 HTML 的元素。
(3)<head>元素内容会先被解析,此时浏览器还没开始渲染页面。
我们看到<head>元素里有用于描述页面元数据的<meta>元素,还有一些<link>元素涉及外部资源(如图片、CSS 样式等),此时浏览器会去获取这些外部资源。除此之外,我们还能看到<head>元素中还包含着不少的<script>元素,这些<script>元素通过src属性指向外部资源。
(4)当浏览器解析到这里时(步骤 3),会暂停解析并下载 JavaScript 脚本。
(5)当 JavaScript 脚本下载完成后,浏览器的控制权转交给 JavaScript 引擎。当脚本执行完成后,控制权会交回给渲染引擎,渲染引擎继续往下解析 HTML 页面。
(6)此时<body>元素内容开始被解析,浏览器开始渲染页面。
在这个过程中,我们看到<head>中放置的<script>元素会阻塞页面的渲染过程:把 JavaScript 放在<head>里,意味着必须把所有 JavaScript 代码都下载、解析和解释完成后,才能开始渲染页面。
到这里,我们就明白了:如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,用户体验会变得很糟糕。
因此,对于对性能要求较高、需要快速将内容呈现给用户的网页,常常会将 JavaScript 脚本放在<body>的最后面。这样可以避免资源阻塞,页面得以迅速展示。我们还可以使用defer/async/preload等属性来标记<script>标签,来控制 JavaScript 的加载顺序。
百度首页
对于百度这样的搜索引擎来说,必须要在最短的时间内提供到可用的服务给用户,其中就包括搜索框的显示及可交互,除此之外的内容优先级会相对较低。
浏览器在渲染页面的过程需要解析 HTML、CSS 以得到 DOM 树和 CSS 规则树,它们结合后才生成最终的渲染树并渲染。因此,我们还常常将 CSS 放在<head>里,可用来避免浏览器渲染的重复计算。
我们知道<p>是 HTML 元素,但又常常将<p>这样一个元素称为 DOM 节点,那么 HTML 和 DOM 到底有什么不一样呢?
根据 MDN 官方描述:文档对象模型(DOM)是 HTML 和 XML 文档的编程接口。
也就是说,DOM 是用来操作和描述 HTML 文档的接口。如果说浏览器用 HTML 来描述网页的结构并渲染,那么使用 DOM 则可以获取网页的结构并进行操作。一般来说,我们使用 JavaScript 来操作 DOM 接口,从而实现页面的动态变化,以及用户的交互操作。
在开发过程中,常常用对象的方式来描述某一类事物,用特定的结构集合来描述某些事物的集合。DOM 也一样,它将 HTML 文档解析成一个由 DOM 节点以及包含属性和方法的相关对象组成的结构集合。
我们常见的 HTML 元素,在浏览器中会被解析成节点。比如下面这样的 HTML 内容:
<html>
<head>
<title>标题</title>
</head>
<body>
<a href='xx.com'>我的超链接</a>
<h1>页面第一标题</h1>
</body>
</html>
打开控制台 Elements 面板,可以看到这样的 HTML 结构,如下图所示:
在浏览器中,上面的 HTML 会被解析成这样的 DOM 树,如下图所示:
我们都知道,对于树状结构来说,常常使用parent/child/sibling等方式来描述各个节点之间的关系,对于 DOM 树也不例外。
举个例子,我们常常会对页面功能进行抽象,并封装成组件。但不管怎么进行整理,页面最终依然是基于 DOM 的树状结构,因此组件也是呈树状结构,组件间的关系也同样可以使用parent/child/sibling这样的方式来描述。同时,现在大多数应用程序同样以root为根节点展开,我们进行状态管理、数据管理也常常会呈现出树状结构。
我们知道,浏览器中各个元素从页面中接收事件的顺序包括事件捕获阶段、目标阶段、事件冒泡阶段。其中,基于事件冒泡机制,我们可以实现将子元素的事件委托给父级元素来进行处理,这便是事件委托。
如果我们在每个元素上都进行监听的话,则需要绑定三个事件;(假设页面上有a,b,c三个兄弟节点)
function clickEventFunction(e) {
console.log(e.target === this); // logs `true`
// 这里可以用 this 获取当前元素
}
// 元素a,b,c绑定
element2.addEventListener("click", clickEventFunction, false);
element5.addEventListener("click", clickEventFunction, false);
element8.addEventListener("click", clickEventFunction, false);
使用事件委托,可以通过将事件添加到它们的父节点,而将事件委托给父节点来触发处理函数:
function clickEventFunction(event) {
console.log(e.target === this); // logs `false`
// 获取被点击的元素
const eventTarget = event.target;
// 检查源元素`event.target`是否符合预期
// 此处控制广告面板的展示内容
}
// 元素1绑定
element1.addEventListener("click", clickEventFunction, false);
这样能解决什么问题呢?
常见的使用方式主要是上述这种列表结构,每个选项都可以进行编辑、删除、添加标签等功能,而把事件委托给父元素,不管我们新增、删除、更新选项,都不需要手动去绑定和移除事件。
如果在列表数量内容较大的时候,对成千上万节点进行事件监听,也是不小的性能消耗。使用事件委托的方式,我们可以大量减少浏览器对元素的监听,也是在前端性能优化中比较简单和基础的一个做法。
注意:
我们了解了 HTML 的作用,以及它是如何影响浏览器中页面的加载过程的,同时还介绍了使用 DOM 接口来控制 HTML 的展示和功能逻辑。我们了解了DOM解析事件委托等相关概念。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>百度一下,你就知道</title>
<style type="text/css">
#img1{
position: relative;
left: 650px;
}
ul li{
float:left;
list-style: none;
padding: 5px;
}
a{
padding: 5px;
text-decoration:none;
}
#baidu{
text-align: center;
}
#input1{
font-size: 30px;
width: 560px;
}
#db{
position:fixed; bottom:0;
}
#div1{
position: absolute;
bottom: 0;
right: 0;
}
#rebang{
font-size: 25px;
position: relative;
left: 610px;
}
ul li div{
display: none;
}
ul li:hover div{
display: block;
}
#C{
font-size: 20px;
float: left;
}
#B{
position: absolute;
margin-left: 260px;
display: none;
}
#A{
font-size: 20px;
width: 1000px;
}
#A:hover #B{
display: block;
}
#D{
font-size: 20px;
float: right;
}
#F{
position: absolute;
display: none;
}
#D:hover #F{
display: block;
}
#G{
height: 50px;
float: right;
}
#H{
background-color: blue;
}
#J{
position: absolute;
right: 800px;
top: 300px;
display: none;
z-index: 2;
}
#G:hover #J{
display: block;
}
#Z{
width: 100px;
height: 30px;
}
#W{
font-size: 22px;
}
</style>
</head>
<body>
<div id="C">
<a href="http://news.baidu.com/" target="_blank">新闻</a>
<a href="https://www.hao123.com/" target="_blank">hao123</a>
<a href="https://map.baidu.com/@12126927,4038819,13z/" target="_blank">地图</a>
<a href="https://haokan.baidu.com/?sfrom=baidu-top/" target="_blank">视频</a>
<a href="https://tieba.baidu.com/index.html/" target="_blank">贴吧</a>
<a href="https://xueshu.baidu.com//" target="_blank">学术</a>
</div>
<div id="A">
<a href="https://www.baidu.com/more//" target="_blank">更多</a>
<div id="B">
<ul>
<li><a href="https://pan.baidu.com/" target="_blank"><img src="imgs/wangpan.png"></img><br>网盘</a></li>
<li><a href="https://zhidao.baidu.com/" target="_blank"><img src="imgs/zhidao.png"></img><br>知道</a></li>
<li><a href="https://baike.baidu.com/" target="_blank"><img src="imgs/baike.png"></img><br>百科</a></li>
<li><a href="https://image.baidu.com/" target="_blank"><img src="imgs/tupian.png"></img><br>图片</a></li>
</ul>
<ul>
<li><a href="https://baobao.baidu.com/" target="_blank"><img src="imgs/baobao.png"></img><br>宝宝</a></li>
<li><a href="https://wenku.baidu.com/" target="_blank"><img src="imgs/wenku.png"></img><br>文库</a></li>
<li><a href="https://jingyan.baidu.com/" target="_blank"><img src="imgs/jingyan.png"></img><br>经验</a></li>
<li><a href="http://music.taihe.com/" target="_blank"><img src="imgs/yinyue.png"></img><br>音乐</a></li>
</ul>
</div>
</div>
<div id="G">
<a href="#"><input type="button" value="登录"></input></a>
<div id="J">
<img src="imgs/JJJ.png" >
</div>
</div>
<div id="D">
<a href="#">设置</a>
<div id="F">
<a href="#">搜索设置</a><br>
<a href="#">高级搜索</a><br>
<a href="#">关闭预测</a><br>
<a href="#">隐私设置</a><br>
</div>
</div>
<div id="img1">
<img src="imgs/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png">
</div>
<div id="baidu">
<a href="#"><input id="input1" type="text" id="" value="" /><input id="Z" type="button" value="百度一下" /></a>
</div>
<div id="rebang">
<a href="http://top.baidu.com/?fr=mhd_card">百度热榜</a>
</div>
<div id="resou" align="center">
<table id="W" border="0" cellspacing="5" cellpadding="25">
<tr>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%E7%89%B9%E6%9C%97%E6%99%AE%E7%A7%B0NBA%E5%83%8F%E4%B8%80%E4%B8%AA%E6%94%BF%E6%B2%BB%E7%BB%84%E7%BB%87&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">1.特朗普炮轰NBA</a></td>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%E5%AE%98%E6%96%B9%E8%B0%83%E6%9F%A5%E5%A5%B3%E6%95%99%E5%B8%88%E4%B8%BE%E6%8A%A5%E6%A0%A1%E9%95%BF%E6%80%A7%E9%AA%9A%E6%89%B0&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">4.官方调查女教师</a></td>
</tr>
<tr>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%E8%94%A1%E8%8E%89%E4%B8%8D%E5%86%8D%E6%8B%85%E4%BB%BB%E6%AD%A6%E6%B1%89%E5%B8%82%E4%B8%AD%E5%BF%83%E5%8C%BB%E9%99%A2%E4%B9%A6%E8%AE%B0&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">2.蔡丽不再担任武汉市医院书记</a></td>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%E9%99%88%E9%98%BF%E5%96%9C%E5%8E%BB%E4%B8%96&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">5.陈阿喜去世</a></td>
</tr>
<tr>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%E5%85%A8%E7%90%83%E8%BF%9110%E4%BA%BF%E4%BA%BA%E5%8F%97%E7%B2%BE%E7%A5%9E%E5%81%A5%E5%BA%B7%E9%97%AE%E9%A2%98%E5%BD%B1%E5%93%8D&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">3.全球近10亿人受精神健康问题影响</a></td>
<td><a href="https://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=31%E7%9C%81%E5%8C%BA%E5%B8%82%E6%96%B0%E5%A2%9E%E5%A2%83%E5%A4%96%E8%BE%93%E5%85%A59%E4%BE%8B&rsv_idx=2&rsv_dl=fyb_n_homepage&hisfilter=1">6.31省区市新增境外输入9例</a></td>
</tr>
</table>
</div>
<div id="db">
<ul>
<li>设为首页</li>
<li>关于百度</li>
<li>About Baidu</li>
<li>百度营销</li>
<li>使用百度前必读</li>
<li>意见反馈</li>
<li>帮助中心</li>
</ul>
</div>
<div id="div1">
<ul>
<li>@2020 Baidu</li>
<li>(京)-经营性-2017-0020</li>
<li>京公网安备11000002000001号</li>
<li>京ICP证030173号</li></ul>
</div>
</body>
</html>
新手勿喷!!!
者公众号:org_yijiaoqian
1.0的HTTP版本,是一种无状态,无连接的应用层协议。 HTTP1.0规定浏览器和服务器保持短暂的链接。
浏览器每次请求都需要与服务器建立一个TCP连接,服务器处理完成以后立即断开TCP连接(无连接),服务器不跟踪也每个客户单,也不记录过去的请求(无状态)。
这种无状态性可以借助cookie/session机制来做身份认证和状态记录。
每次发送请求,都需要进行一次TCP连接,而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率变低。
由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送,假设前一个请求响应一直不到达,那么下一个请求就不发送,后面的请求就阻塞了。
HTTP1.1继承了HTTP1.0的简单,克服了HTTP1.0性能上的问题。
HTTP1.1增加Connection字段,通过设置Keep-Alive保持HTTP连接不断卡。避免每次客户端与服务器请求都要重复建立释放建立TCP连接。提高了网络的利用率。
如果客户端想关闭HTTP连接,可以在请求头中携带Connection:false来告知服务器关闭请求。
HTTP1.1支持请求管道化(pipelining)。
基于HTTP1.1的长连接,使得请求管线化成为可能。 管线化使得请求能够“并行”传输。
例如:
假如响应的主体是一个html页面,页面中包含了很多img,这个时候keep-alive就了很大作用。能够“并行”发送多个请求。(注意,这里的“并行”并不是真正意义上的并行传输)
需要注意的是:服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。
也就是说,HTTP管道化可以让我们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列)
如果,客户端同时发了两个请求分别获取html和css,假如说服务器的css资源先准备就绪,服务器也会先发送html,再发送css。 换句话来说,只有等到html响应的资源完全传输完毕后,css响应的资源才开始传输,不允许同时存在两个并行的响应。
可见,HTTP1.1还是无法解决队头阻塞(head of line blocking)的问题。同时“管道化”技术存在各种各样的问题,所以很多浏览器要么根本不支持它,要么直接默认关闭,并且开启的条件很苛刻……而且好像实际也没有什么用处。
HTTP1.1支持管道化,但是服务器也必须进行逐个响应的送回,这个是很大的一个缺陷。实际上,现阶段的浏览器厂商采取了另外一种做法,它允许我们打开多个TCP的会话,也就是说,上图我们看到的并行,其实是不同的TCP连接上的HTTP请求和相应。这才是真正的并行!
很多人以为的连接数情况:
实际情况(china):
此外,HTTP1.1还加入了缓存处理(强缓存和协商缓存),新的字段如cache-control,支持断点传输,以及增加了Host字段(使得一个服务器能够用来创建多个Web站点)
HTTP2.0通过在应用层和传输层之间增加一个二进制分层帧,突破了HTTP1.1的性能限制,改进传输性能。
所有HTTP2.0通信都在一个TCP链接上完成,这个链接可以承载任意流量的双向数据流。
每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(Stream_id)重新封装。
多路复用(连接共享)可能会导致关键字被阻塞,HTTP2.0里每个数据流都可以设置优先级和依赖,优先级高的数据流会被服务器优先处理和返回客户端,数据流还可以依赖其他的子数据流。
可见,HTTP2.0实现了真正的并行传输,它能够在一个TCP上进行任意数量的HTTP请求。而这个强大的功能基于“二级制分帧”的特性。
在HTTP1.X中,头部元数据都是以纯文本的形式发送的,通常会给每个请求增加500-8000字节的负荷。
比如cookie,默认情况下,浏览器会在每次请求的时候,把cookie附在header上面发给服务器。
HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header_files表,既避免重复header的传输,又减少了需要传输的大小。
高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。
服务器除了最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确的需求。
Google搞了一个基于UDP协议的QUIC协议,并且使用在了HTTP/3上, HTTP/3之前的名称为HTTP-over-QUIC。
早期Quic协议,存在IETF和Google两个版本,直到它被证实命名为HTTP3.0
IETF的QUIC工作小组创造了QUIC传输协议。QUIC是一个使用UDP来替代TCP的协议。最初的时候,Google开始助力QUIC,其后QUIC更多地被叫做“HTTP/2-encrypted-over-UDP “。
社区中的人们已经使用非正式名称如iQUIC和gQUIC来指代这些不同版本的协议,以将QUIC协议与IETF和Google分开(因为它们在细节上差异很大)。通过“iQUIC”发送HTTP的协议被称为“HQ”(HTTP-over-QUIC)很长一段时间。
2018年11月7日,Litespeed的Dmitri宣布他们和Facebook已经成功地完成了两个HTTP/3实现之间的第一次互操作。Mike Bihop在该主题的HTTPBIS会话中的后续介绍可以在这里看到。会议结束时达成共识称新名称是HTTP/3!
缓存当前会话的上下文,下次恢复会话的时候,只需要将之前的缓存传递给服务器,验证通过,就可以进行传输了。
0-RTT建连可以说是QUIC相比HTTP2最大的性能优势。
什么是0-RTT建连?
QUIC基于UDP,一个连接上的多个stream之间没有依赖,即使丢包,只需要重发丢失的包即可,不需要重传整个连接。
QUIC在移动端的表现比TCP好,因为TCP是基于IP识别连接,而QUIC是通过ID识别链接。 无论网络环境如何变化,只要ID不便,就能迅速重新连上。
TCP协议头没有经过任何加密和认证,在传输过程中很容易被中间网络设备篡改、注入和窃听。
QUIC的packet可以说武装到了牙齿,除了个别报文,比如PUBLIC_RESET和CHLO,所有报文头部都是经过认证的,报文Body都是经过加密的。
所以只要对 QUIC 做任何更改,接收端都能及时发现,有效地降低了安全风险。
QUIC协议有一个非常独特的特性,称为向前纠错(Foward Error Connec,FEC),每个数据包除了它本身的内容之外还包括了其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。
向前纠错牺牲了每个数据包可以发送数据的上限,但是带来的提升大于丢包导致的数据重传,因为数据重传将会消耗更多的时间(包括确认数据包丢失,请求重传,等待新数据包等步骤的时间消耗)。
例如:
没有必要。
在头部压缩技术中,客户端和服务器均会维护两份相同的静态字典和动态字典。
在静态字典中,包含了常见的头部名称与值的组合。静态字典在首次请求时可以使用。那么现在头部的字段就可以被简写成静态字典中相应字段的index。
而动态字典跟连接的上下文相关,每个HTTP/2连接维护的动态字典不尽相同。动态字典可以在连接不停地进行更新。
也就是说,原本完整的HTTP报文头部的键值或字段,由于字典的存在,现在可以转换成索引index,在相应的端再进行查找还原,也就起到了压缩的作用。
所以,同一个链接上产生的请求和响应越多,动态字典累积得越全,头部压缩的效果也就越好,所以针对HTTP/2网站,最佳实践是不要合并资源。
另外,HTTP2.0多路复用,使得请求可以并行传输,而HTTP1.1合并请求的一个原因也是为了防止过多的HTTP请求带来的阻塞问题。而现在HTTP2.0已经能够并行传输了,所以合并请求也就没有必要了。
由于HTTP/2使用了多路复用,一般来说,同一个域名下只需要使用一个TCP链接,但当这个连接中出现了丢包的情况,就会导致HTTP/2的表现情况反倒不如HTTP/2了。
原因是: 在出现丢包的额情况下,整个TCP都要开始等待重传,导致后面的所有数据都被阻塞。
但是对于HTTP/1.1来说,可以开启多个TCP连接,出现这种情况只会影响其中一个连接,剩余的TCP链接还可以正常传输数据。
由于修改TCP协议是不可能完成的任务。
MTF在资源服务器和内容分发节点都已经启用了 HTTP3.0 协议,根据 用户浏览器 向下兼容,强烈建议您在Chrome浏览器开启实验性QUICK协议支持,体验加速效果:
基于UDP实现
0RTT建连
基于UDP的多路复用
加密认证的报文
向前纠错机制
文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读, 本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。
*请认真填写需求信息,我们会在24小时内与您取得联系。