前端性能优化
规则1—— 减少HTTP请求
用户看到绚丽多彩的网页之前
- 80% 的时间用于加载HTML文档所需的 图片、js、css、等资源进行的 http请求上。
- 因此改善响应时间最简单的途径就是 减少 http请求数量。
图片地图
- 上古时代~ 至少我没用过
CSS Sprites
- 几乎现在没人用了—— 上古时代的 精灵图 or 雪碧图
- 多个图片合并在一张大图上
内联图片
将图片转化为 base64码。这样就不会去请求资源了,
<img src="data:image/png;base64,iVAGRw0KGDCFGNSUhEUgACBBQAVGADCAIATYJ7ljmRGGAAGElEVQQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPACCCTElFTEVBQmGA" />
优点
- 减少http请求次数
- 做为背景平铺类的图片使用内联图片的话,减少http请求次数,并且不会影响加载速度
使用内联图片的缺点
- 浏览器不会缓存内联图片资源
- 兼容性较差,只支持ie8以上浏览器
- 超过1000kb的图片,base64编码会使图片大小增大,导致网页整体下载速度减慢
合并脚本和样式表
- 当今最流行的单页应用的构建过程
- 打包js 、css
- 压缩js 、css
规则2——使用内容发布网络 CDN
CDN 一组分布在不同地理位置的web服务器,你访问这些资源的时候会选择离你位置最近的服务器把资源返回。
- 用于发布静态资源如 图片、js、css等
规则3——使用 Expires头
- Expires头就是给你一个过期时间 年月日时分秒,
- 你请求一个图片资源时,回去对比这个时间 如果没到就不发请求
Expires头缺点:
- 设置的过期时间是 2024年,但你系统时间是2050年~ 永远都会去发请求
Max-Age 和 mod_expires
- HTTP1.1 引入了 Cache-Control头 ,解决了 Expires头的问题
- 通过 Cache-Control: max-age=31536000 指定过期秒数
如何更新缓存
假设你的一个图片<img src="/xx.png"/>
如果你想更新它,但是它设置的 Expires 还没过期,此时即使你服务器端把文件替换了,但是用户看到的还是老的图片
- 第一个方法 改文件名
<img src="/yy.png"/>
- 第二个方法 时间戳
<img src="/xx.png?t=20200330152222"/>
规则3——压缩组件
HTTP实际就是本质就是“字节流”
所以传输过程中,文件的大小也会影响时间。200K的文件 压缩后可能是 100K ,这样在下载速度恒定的情况下,下载总耗时就少了
- Accept-Encoding头
请求头
Accept-Encoding: gzip, deflate, br
响应头
Content-Encoding: gzip
gzip
gzip压缩通常能压缩 66%的内容
代理缓存
- 正常情况浏览器直接和服务器通信,没有任何问题,web服务器会根据 Accept-Encoding 来检测是否响应进行压缩。
- 浏览器会基于 Cache-control 来缓存文件
过程
- 001 当你的浏览器通过代理 访问 URL,而恰巧这个浏览器不支持 gzip ,此时代理就缓存一个 未经过gzip版本的文件。
- 002 另一个浏览器通过代理访问服务器,这个浏览器支持 gzip,由于代理缓存的是 未压缩的版本,而代理会使用 未压缩版的缓存文件处理响应,这样得到的 就是未压缩的文件。
解决代理缓存的问题就是
- 设置 Vary:Acceipt-Encoding 这将使代理缓存多个版本的文件。 压缩版和未压缩版
规则5——将样式表放在顶部
白屏问题
- 就是刷新页面 半天白屏然后才显示页面
将CSS放在顶部
两种方式
<link rel="stylesheet" href="main.css">
<style>
@import url("main.css")
</style>
- 推荐使用 link 代替 @import
- 一个 style可以包含多个 @import 规则,但 @import 规则必须放在所有其他规则之前。
- @import 可能会导致白屏,即便把他放到 head 里也是如此
- 使用 @import 会导致组件下载时的无序性。
测试, 一个HTML页面 6个图片,一个 css
- link 在 Head里 , 先 html,然后css ,最后图片
- link 在 body里后, 先 html ,然后图片,然后css
- @import 在 head里, 先html,然后图片,最后css
一个有意思的对比故事
相传 在老版本的IE是女汉子,chrome是小姐姐
- 女汉子:出门在化妆
- 在 IE你会先看到 html结构,然后样式逐渐呈现
- 小姐姐:不化妆不出门
- 在 chrome里, 如果css不加载完,我就不显示
规则6——将脚本放在底部
上一规则是把 样式表前置到 Head里让页面先呈现,而这次是把 js放在底部。
这样页面既可以逐步呈现,也可以提高下载的并行度
并行下载
对响应时间影响最大的是页面中组件的数量。当缓存为空时,每个组件都会产生一个HTTP请求,有时即使缓存是完整的也是如此。
浏览器会并行的执行HTTP请求,为什么HTTP请求的数量会影响响应时间呢?难道不能一次下载所有吗?
- HTTP1.1 规范,该规范建议浏览器从每个主机名并行的下载两个组件(https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4)
- 这样请求一个网站加载各种组件的时候,应该呈现 2个请求一组的 阶梯状下载的
- 如果一个 web页面平均的将他的组件分别放在 两个主机名下,整体响应时间可以减少大约一半
- 现在很多网站使用 HTTP1.1 ,但是他们的并行下载数量 大部分已经不是2个了。
增加并行下载数量比如8个,并不是没有开销的,其优劣取决于你的带宽和CPU速度,过多的并行下载反而会降低性能
脚本阻塞下载
- 脚本可能使用 document.write 来修改页面内容,因此浏览器会等待,以确保页面能恰当的布局
- 在浏览器下载脚本时,浏览器阻塞并行下载的另一个原因是:
- 为了保证脚本能按照正确的顺序执行
- 如果并行下载多个脚本,就无法保证响应是按照特定顺序达到浏览器的。
<!DOCTYPE html> <html lang="en"> <body> <img src="1.png" alt=""> <img src="2.png" alt=""> <script src="main.js"></script> <img src="3.png" alt=""> <img src="4.png" alt=""> </body> </html> 如果 main.js 很大, 会阻塞加载 3.png 和 4.png
- 这样是为了保证了 如果 多个脚本存在依赖关系的情况下保证 js的执行顺序
最差情况:将 js放在顶部
- 脚本会阻塞对后面内容的呈现
- 脚本会阻塞对其后面组件的下载
最佳情况:将js放在底部
- 这样不会阻塞页面内容的呈现
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="main.css">
</head>
<body>
<img src="1.png" alt="">
<img src="2.png" alt="">
<img src="3.png" alt="">
<img src="4.png" alt="">
<script src="main.js"></script>
</body>
</html>
参考一些网站的页面html结构即可
规则7——避免使用CSS表达式
CSS表达式(css expression)可以动态设置CSS属性的强大(并危险)的方式
left: expression(document.body.offsetWidth - 180 "px");
top: expression(document.body.offsetHeight - -80 "px");
background:expression( (new Date()).getHours()%2 ?red:green);
- 更新表达式会造成频繁计算
width:expression(setCntr(),document.body.clientWidth<600 ? '600px':'auto');
CSS exprssion技术达到了可以使用表达式或公式来定义CSS属性的目的,MSDN针对CSS表达式给出的优点是:减少页面上的代码,使设计师无需学习JavaScript就能实现一些DHTML的效果。但这种减少代码主要是减少了JavaScript的代码。
CSS表达式缺陷:
- 不符合WEB标准CSS表达式这种在表现中插入行为的JavaScript代码,有悖于Web标准的结构、表现、行为相分离的理念。
- 效率低一个CSS表达式会反复执行,因为需要不停的去计算CSS的属性值,甚至执行成百上千次,这会大大消耗计算机硬件资源,极端情况下可能会导致浏览器崩溃。
- 安全隐患CSS表达式暴露了一个脚本执行的上下文,可能带来脚本注入的隐患。
结论:无论何时都别用CSS表达式
规则8——使用外部JS和CSS
内联VS外置
纯粹而言,内联快一点
- 内联:一个html ,js css都写在 html里
- 它实际就触发一次请求
- 外置:一个html 引入 js css
- 至少三个http请求,虽然资源大小可能一样,而且每个请求都有耗时
- 优点是: js和css可以被缓存,其实也就是第一次慢~
页面浏览量
- 如果一段js/css被多页页面共用,当用户访问 1.html 时候会被缓存,在访问 2.html时就不会重新下载了
- 使用外部 js、css的收益随着用户每月页面的浏览量而增加
空缓存 VS 完整缓存
- 用户第一次请求 一个页面 由于 空缓存,每个资源都要下载一次
- 用户第二次请求这个页面 由于部分 js \ css 已被缓存,则页面响应速度变快
组件重用
- 多个页面使用相同的 js \ css 使用外部引入可以提高组件的重用率
- 因为第二次访问的时候已经被缓存了
主页响应速度问题
有些主页为了保证响应速度内联 js和css。
但是他们还有其他页面 如 about.html / help.html
- 其他页面使用了 外置的 js / css 该如何解决呢?
- 首页加载后下载 在 home onload事件里 动态加载 js / css
- 如果想避免对 home造成影响可以 使用 iframe 指定 外置 js/css的 url
动态内联
通过cookie,如果你是第一次访问本网站,肯定不 cookie,此时执行 下载外置 js \ css的逻辑
规则9——减少DNS查找
Internet 是通过 IP地址查找服务器的。 由于IP 很难记忆,于是采用 包含主机名的 URL代替。 当浏览器发送请求时,IP地址仍然需要,这就是 DNS 所处的角色。
- DNS将主机名映射到 IP 上。 这样你访问 taobao.com 的时候就会去查找 淘宝对应的 IP.
DNS也是开销。
浏览器查找一个给定主机名的IP 地址需要20~120毫秒。在DNS找到之前,浏览器不能从主机下载到任何东西。
DNS缓存和TTL
当你访问 淘宝 发生了什么
- 你请求一个主机后,DNS信息会缓存在操作系统的DNS缓存里。之后再次访问就无须DNS查找
- 很多浏览器拥有自己的缓存,和操作系统缓存相分离,只要浏览器保留了 DNS缓存信息,就不会麻烦操作系统记录这个记录。
影响DNS缓存的因素
- 服务器可以表明记录可以缓存多久,查找返回的DNS记录 包含了一个存活事件TTL值,该值告诉客户可以对该记录缓存多久
Keep-Alive
- 默认情况下,一个持久的TCP连接会一直使用,直到其空闲1分钟为止。
- 由于连接是持久的,因此无须 DNS查找
- 另一个优点是:Keep-Alive 通过现有连接避免了重复的DNS查找
减少DNS查找
- 当客户端 DNS缓存为空,DNS查找数量与 Web页面里唯一主机名的数量相等,包含 页面url,图片,js,css,等
- 减少唯一主机名的数量可以减少 DNS查找数量
- 尽可能使用持久连接,以消除TCP握手和慢启动延迟。
每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续的请求。
规则10——精简JS
- 去除注释
- 混淆
- 压缩代码
现代前端各种 打包工具,基本实现了这个功能
规则11——避免重定向
- 301
- 302
重定向会让你的页面变慢
重定向类型
- 300 Multiple Choices
- 301 Moved Permancently
- 302 Moved Temporarily
- 303 See Other
- 304 Not Modified
- 305 Use Proxy
- 306 不再使用
- 307 Temporary Redirect
301/302都不会被缓存
- 除非设置 Expires 和 Cache-Control
HTTP重定向很费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的DNS查询、TCP握手,还有其他延迟。最好的重定向次数为零。
规则12——删除重复脚本
确保脚本只被包含一次
规则13——配置ETag
ETag 是一种缓存机制
- MD5摘要算法,变动越小差异越大
- 第一次文件响应的时候 返回一个 文件 md5值
- 下次请求同一资源的时候发送这个 md5 如果不同则重新下载,相同则不下载
详情参考 https://sltrust.github.io/2018/02/10/N044_02_Cache_Control/
- 用 Cache Control是直接不请求,从盘符里读取资源
- 用 ETag 是直接不下载,但是仍然会发请求
规则14——使用Ajax可缓存
比如 ajax请求 一些内容是万年不变的,
- 这个时候如果用 Expires 就能从浏览器的缓存读取
但我感觉这可能是上古时代的场景,已经不适用于高速发展的今天了