目录
1.从输入URL到页面加载完成,完整的链路
1.DNS解析
2.TCP连接
3.HTTP请求抛出
4.服务器处理请求,HTTP响应返回
5.浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户
2.整个性能消耗
http 网络层面
1.DNS解析
DNS实现域名到ip的映射。通过域名访问站点,每次请求都要做DNS解析。目前每次DNS解析
,通常在200ms以下。一般采用DNS Prefetch一种DNS预解析技术。当浏览网页时,浏览器会在加载网页时对网页中的域名进行解析缓存,这样在点击当前网页的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。
<link rel="dns-prefetch" href="www.baidu.com" />
只有部分浏览器支持
2.TCP连接,使用HTTP2
HTTP2相比HTTP1.1有如下优点
1.解析速度快
服务器解析HTTP1.1的请求时,必须不断地读入字节,直到遇到分隔符CRLF为止
解析HTTP2的请求不用这么麻烦,HTTP2是基于帧的协议,每个帧都要表示帧长度的字段
2.多路复用
HTTP1.1如果要同时发起多个请求,就得建立多个TCP连接,因为一个TCP连接同时只能处理一个HTTP1.1的请求。
在HTTP2上,多个请求可以公用一个TCP连接,这就称为多路复用,同一个请求和响应用一个流来表示,并有唯一的流ID来标识。多个请求和响应在TCP连接中可以乱序发送,到达目的地后再通过流ID重新组建
3.首部压缩
HTTP提供首部压缩功能
例如有如下两个请求:
:authority: unpkg.zhimg.com
:method: GET
:path: /za-js-sdk@2.16.0/dist/zap.js
:scheme: https
accept:
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
:authority: zz.bdstatic.com
:method: GET
:path: /linksubmit/push.js
:scheme: https
accept:
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
可以看出,有很多数据重复,如果可以把相同的首部存储起来,只发送他们之间不同的部分,就可以节省流量,加快请求时间
HTTP2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送
4.优先级
HTTP2 可以对比较紧急的请求设置一个较高的优先级,服务器在收到这样的请求后,可以优先处理
5.流量控制
由于一个 TCP 连接流量带宽(根据客户端到服务器的网络带宽而定)是固定的,当有多个请求并发时,一个请求占的流量多,另一个请求占的流量就会少。流量控制可以对不同的流的流量进行精确控制。
6.服务器推送
HTTP2 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求
3.浏览器并发
基于端口跟线程切换开销,浏览器不可能无限的并发请求。chrome的并发为6,超过限制数目的请求就会被阻塞;
对于某些静态资源,图片等等,我们可以对其URL分散处理 ,不同的资源域名(部署在cdn上)。
4.http请求次数
减少http的请求次数,将多个请求合并成同一个,减少http的开销
5.webpack
充分利用webpack提供给我们的能力,利用DllPlugin与commonPlugins等插件对我们代码进行
优化,文件的分割与合并,公共代码的提取,长缓存等策略,webpack是个很好的东西,值得大家仔细研究
6.http压缩
采用Gzip压缩:HTTP 压缩就是以缩小体积为目的,对 HTTP 内容进行重新编码的过程,原理是找出一些重复出现的字符串、临时替换它们,从而使整个文件变小,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大,可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能
webpack 和 node 配置 gzip 的使用方法。
npm install compression-webpack-plugin --save-dev
npm install compression
webpack 配置
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [new CompressionPlugin()],
}
node 配置
const compression = require('compression')
// 在其他中间件前使用
app.use(compression())
图片优化
1.雪碧图
优点:任何用到页面图片的场景。将多个页面用到的北京图片合并成一个大的图片在页面中引用,可以有效地减少请求个数。
缺点:但是当整合图片比较大时,一次加载比较慢。
2.Base64
优点:适用于图片小于2KB,页面引用图片不多的情况。将图片转换为base64编码字符串inline到CSS或页面中,减少http的请求次数。
缺点:由于Base64编码用8位字符表示信息中的6个位,所以编码后大小大约比原始值扩大了 33%
3.使用canvas代替图片
需要高性能的图片或动画,使用HTML5的canvas元素绘制图片,页面渲染性能较高
4、响应式图片
不同终端对同一图片的需求不一样,根据终端加载不同的图片来节省不必要的流量。通过picture元素,picturefill或平台判断来为不同终端平台输出不同的图片。减少没必要的图片加载,灵活控制。
响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片。
通过 picture
实现
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img src="banner_w800.jpg" alt="">
</picture>
通过 @media
实现
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
5、图片压缩
在不得不加载图片的前提下,进一步提高优化效果,通过有损或无损压缩所见图片的大小。减少图片加载流量,效果明显。一是通过 webpack 插件 image-webpack-loader
,二是通过在线网站进行压缩。
npm i -D image-webpack-loader
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
/*对图片进行压缩*/
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
6、更好的图片格式
webp、bpg、sharpP等新图片格式具有更好的压缩比
WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。
7.图片懒加载
图片懒加载在一些图片密集型的网站中运用比较多,通过图片懒加载可以让一些不可视的图片不去加载,避免一次性加载过多的图片导致请求阻塞(浏览器一般对同一域名下的并发请求的连接数有限制),这样就可以提高网站的加载速度,提高用户体验。
原理
将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名,我才用data-src)属性指向真实的图片。注意,图片要指定宽高。
<img src="default.jpg" data-src="666.jpg" />
第一种是纯粹的延迟加载,使用seTimeOut或setInterval进行加载延迟
第二种是条件加载,符合某些条件,或触发了某些事件才开始异步加载
第三种是可视区域加载,即只加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在某用户看到某图片前一定距离开始加 载,这样能保证用户下拉时正好能看到图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
img {
display: block;
margin-bottom: 50px;
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/1.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/2.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/3.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/4.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/1.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/2.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/3.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/4.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/1.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/2.jpg" alt="">
<img src="../assets/loading.gif" data-src="../assets/images/puzzle/3.jpg" alt="">
<script>
let num = document.getElementsByTagName('img').length;
let img = document.getElementsByTagName("img");
let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); //页面载入完毕加载可是区域内的图片
window.onscroll = lazyload;
function lazyload() { //监听页面滚动事件
let seeHeight = document.documentElement.clientHeight; //可见区域高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (let i = n; i < num; i++) {
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "../assets/loading.gif") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
</script>
</body>
</html>
8.使用字体图标来代替图片
缓存
浏览器缓存
强缓存
浏览器在请求某一资源时,会先获取该资源缓存的header信息,判断是否命中强缓存(cache-control和expires信息),若命中直接从缓存中获取资源信息,包括缓存header信息;本次请求根本就不会与服务器进行通信。
Cache-Control用于控制文件在本地缓存有效时长。
最常见的,比如服务器回包:Cache-Control:max-age=600表示文件在本地应该缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出HTTP请求,而是直接使用本地缓存的文件。
Cache-Control还有一个同功能的字段:Expires。Expires的值一个绝对的时间点,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在这个时间点之前,缓存都是有效的。
Expires是HTTP1.0标准中的字段,Cache-Control是HTTP1.1标准中新加的字段,功能一样,都是控制缓存的有效时间。当这两个字段同时出现时,Cache-Control是高优化级的。
协商缓存(对比缓存)
Last-Modified和If-Modified-Since字段:
1、浏览器第一次向服务器发请求,服务器返回资源并在response header加上Last-Modified字段,表示资源最后修改的时间。
2、浏览器再次请求这个资源时,请求头会加上If-Modified-Since字段。若这两个字段一样,说明资源没有修改过,返回304Not Modified,浏览器从缓存中获取资源。若这两个字段不一样,说明资源修改过,服务器正常返回资源。
Cache-Control通常与Last-Modified一起使用。一个用于控制缓存有效时间,一个在缓存失效后,向服务查询是否有更新。
Etag和If-None-Match字段:
服务器会为每个资源生成一个唯一的标识字符串,只要文件内容不同,它们对应的 Etag 就是不同的;If-Modified-Since能检查到的精度是s级的,某些服务器不能精确的得到文件的最后修改时间,我们编辑了文件,但文件的内容没有改变。因为服务器是根据文件的最后修改时间Last-Modified来判断的,导致重新请求所以才出现了Etag,Etag对服务器也有性能损耗
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
memory cache 与 disk cache
这两者都是强缓存的结果,其中前者表示缓存来自内存,这种缓存会随着浏览器关闭而消失。后者表示缓存来自硬盘,不会随着浏览器的关闭而消失。控制到底是from memory cache还是from disk cache是通过响应头的Etag来实现的。如果有Etag字段,那么浏览器会将本次缓存写入硬盘。
浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
CDN缓存
CDN全称是Content Delivery Network,即内容分发网络,它能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度
应用缓存
Cookie:同一个域名下的所有请求,都会携带 Cookie,大小限制4kb
Session Storage:用来存储生命周期和它同步的会话级别的信息,关闭浏览器就不存在
Local Storage:持久化缓存 5-10Mb
Service Worker缓存:
pwa,会拦截http请求,对资源进行离线缓存、消息推送,无法直接访问dom,
利用workbox插件非常容易接入pwa技术
浏览器渲染
浏览器渲染机制
DOM树:
解析 HTML 以创建的是 DOM 树(DOM tree ):渲染引擎开始解析 HTML 文档,转换树中的标签到 DOM 节点,它被称为“内容树”。
CSSOM树:
解析 CSS(包括外部 CSS 文件和样式元素)创建的是 CSSOM 树。CSSOM 的解析过程与 DOM 的解析过程是并行的。
-渲染树:
CSSOM 与 DOM 结合,之后我们得到的就是渲染树(Render tree )。
布局渲染树:
从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标,我们便得到了基于渲染树的布局渲染树(Layout of the render tree)。
绘制渲染树:
遍历渲染树,每个节点将使用 UI 后端层来绘制。整个过程叫做绘制渲染树(Painting the render tree)。
回流、重绘
回流(重排)指的是,当元素的布局或显示等信息改变时,引起 Render 树部分或整体的重新构建,即网页布局的调整。重绘指的是,Render 树中节点属性的更新。因此,回流必然会引起重绘,重绘不一定引起回流。与布局无关的元素属性操作将只引起重绘,否则将同时引起回流和重绘。可能引起回流、重绘的行为如下:
- 添加、删除元素(回流+重绘)
- 隐藏元素:display:none(回流+重绘);visibility:hidden(只重绘,不回流)
- 移动元素:改变 top,left(不一定引起回流);将元素移动到另一个父元素中(回流+重绘)等
- 改变 style:布局相关属性如 padding, border-width, font-size(回流+重绘);布局无关属性(只重绘,不回流)。可查看 https://csstriggers.com/ 以获取更多信息
- 用户操作:改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
回流的开销比重绘大。如果 body 顶部插入一个节点,将引起整个 body 的回流;如果尾部插入一个节点,将只引起部分内容的回流,因此开销较小。浏览器为提升回流、重绘的性能,会构建队列以执行批量处理。但是,访问节点的 offsetTop, scrollTop, clientTop, width, height 类属性或调用了 getComputedStyle 方法,将迫使浏览器提前执行队列以获得最新的样式。减少回流、重绘的方法如下:
- className 或 cssText 批量更新样式,避免单属性操作引起的频繁回流或重绘
- 新创建的元素改完样式后,再插入文档;或者先将节点的 display 属性置为 none,调整样式后再置回显示状态
- 使用变量缓存元素的样式,避免频繁读取元素的样式,致使浏览器提前执行回流队列(以计算元素的布局信息)
- 指定图片的宽高,避免新加载的图片调整大小时引起的回流
- [慎用]使频繁回流、重绘的元素单独有一个 RenderLayer:借助 video 元素、WebGL、Canvas、CSS3 3D、CSS滤镜、z-index 大于某个相邻节点的元素都会有独立的 RenderLayer,比如通过添加 transform: translateZ(0); backface-visibility: hidden; 样式即可。
回流、重绘可借助 dynaTrace(测试ie)、Speed Tracer(测试Chrome) 测试。