专栏首页Super 前端高性能网站建设指南-前端性能优化(一)

高性能网站建设指南-前端性能优化(一)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://ligang.blog.csdn.net/article/details/63258757

​ 年前,读完了《高性能网站建设指南》,但是一直没有整理。年后回来和同事一起出了份前端面试题,涉及到了关于性能优化的问题,在此特梳理一下。

​ 大量的公司在开发功能业务时,只关注功能点的实现,对于性能方面要求很低甚至不作为考虑范围。在遇到一些性能瓶颈时,也往往通过加机器的暴力方式去减缓,但那并不是解决问题的根本。作为前端工程师,大部分人为了迎合需求一直在学习JavaScript、CSS、HTML5及Node,很少去关注性能方面的东西。然而,有些性能的优化点只需要花费很少的时间和精力就能换来巨大的改善用户体验。在陈述前端性能优化的问题之前,我们先思考如下问题:

一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

  1. 在浏览器输入地址;
  2. 浏览器查找域名的 IP 地址,包括 DNS 具体的查找过程,包括:浏览器缓存、系统缓存、路由器缓存等;
  3. 浏览器向 web 服务器发送一个 HTTP 请求;
  4. 服务器的永久重定向响应(从 http://example.comhttp://www.example.com);
  5. 浏览器跟踪重定向地址;
  6. 服务器处理请求;
  7. 服务器返回一个 HTTP 响应;
  8. 浏览器渲染显示 HTML
  9. 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSSJS等等);
  10. 浏览器发送异步请求。

参考地址:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/

性能黄金法则:只有10%-20%的最终用户响应时间花在了下载HTML文档上。其余的80%-90%时间花在了下载页面中的所有组件上。

写在前面

​ 在阐述优化点之前,有必要的先说明一下HTTP,其作为浏览器和服务器之间的一种传输协议,在整个过程中的作用至关重要!对HTTP更多的了解,推荐阅读《HTTP权威指南》。

压缩

压缩响应是最卓有成效的优化方式,浏览器可以使用Accept-Encoding头来声明支持压;服务器使用Content-Encoding头确认响应已被压缩。

==Request Headers==
Accept-Encoding:gzip

==Response Headers==
Content-Encoding:gzip

条件GET请求

​ 如果浏览器在其缓存中保留了组件的一个副本,但并不确定它是否仍然有效,就会生成一个条件Get请求。如果确认缓存在副本仍然有效,浏览器就可以使用缓存中的副本。

​ 典型情况下,缓存副本的有效性源自其最后修改时间。基于响应中的Last-Modified头,浏览器可以知道组件最后的修改时间。它会使用If-Modified-Since头将最后修改时间发送给服务器。如果组件没有被修改过,服务器会返回一个“304 Not Modified”状态码并不再发送响应体,从而得到一个更小且更快的相应。在HTTP1.1中可以使用ETag和If-None-Match进行条件GET请求(下面讲述)。

==Request Headers==
If-Modified-Since:Thu, 07 Apr 2016 08:30:15 GMT

==Response Headers==
Last-Modified:Thu, 07 Apr 2016 08:30:15 GMT

Expires

​ 条件GET请求和304响应有助于让页面加载得更快,但仍需要在客户端和服务器之间进行一次往返确认,以执行有效性检查。Expires头明确指出浏览器是否可以使用组件的缓存副本。如果组件没有过期,浏览器就会使用缓存版本而不会进行任何HTTP请求。

==Response Headers==
Expires:Thu, 07 Apr 2019 08:30:15 GMT

Keep-Alive

​ HTTP构建在传输控制协议TCP(Transmission Control Protocol)之上。HTTP早期实现中,每个HTTP请求都要打开一个socket连接。持久连接可以确保在单独的连接上进行多个请求。浏览器和服务器使用Connection头来指明对Keep-Alive的支持。在HTTP1.1中并不是必须的,HTTP1.1中定义的管道可以在一个单独的socket上发送多个请求,管道性能优于持久连接。但IE7不支持,所以很多浏览器和服务器仍然包含Keep-Alive。

==Request Headers==
Connection:keep-alive

==Response Headers==
Connection:keep-alive

规则1:减少HTTP请求

性能黄金法则中提到80%~90%时间花在HTML文档中组件下载。因此,减少组件的数量,并由此减少HTTP请求的数量。是改善响应时间的最简单途径。

图片地图

​ 对于“图片超链接”的情况,可以使用图片地图减少页面图片个数,从而减少HTTP请求。其分为服务器端图片地图和客户端图片地图,详见:HTML5-嵌入内容

CSS Sprites

​ 同图片地图,CSS Sprites也可合并图片,将多个图片合并到一个单独的图片中,使用CSS的background-position属性将HTML元素放到背景图片中期望的位置上。

<div style="background-image: url('sprites.git'); 
            background-position: -260px -90px; 
            width: 26px; height: 26px;">
</div>

注意:图片地图中的图片必须是连续的,而CSS Sprites则没有这个限制。

内敛图片

​ 通过使用data: [<mediatype>][;base64],<data>模式可以在Web页面中包含图片,而无需额外的HTTP请求(IE不支持)。要注意,在跨页面时不会被缓存。不要去内联公司的logo,因为编码过的logo会导致页面变大。聪明的做法是:使用CSS将内联图片作为背景,将其放在外部样式表中,数据可以缓存在样式表内部。虽然将内联图片放置在外部样式表中增加了一个额外的HTTP请求(请求样式表),但被缓存后可以得到额外的收获。当然,对于只使用一次(如,验证码)直接可以写在页面上。

示例:存放到样式表

.cart {background-img: url(data:image/png;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDA...)}

示例:直接页面嵌入

<img src="data:image/png;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDA..." />

合并脚本和样式表

​ 合并脚本和样式表,是最普通不过的性能优化方式,可以使用Grunt、Webpack、Gulp等工具,这里不再赘述。需要思考的是:一个多页面的网站会有大量的模块,而模块的组合情况复杂,如何合并模块值得花时间去分析一下自己的页面,确保组合的数量是可管理的。

规则2:使用内容发布网路

​ 内容发布网路(CDN)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容,其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。使用CDN,需要注意:更新内容后,CDN的生效时间!

规则3:添加Expires头

​ Expires头在前面已经阐述过,其目的主要是最大化利用浏览器的缓存来改善页面的性能。

Expires头

​ 浏览器(和代理)使用缓存来减少HTTP请求的数量,并减少HTTP响应的大小。

==Response Headers==
Expires:Thu, 07 Apr 2019 08:30:15 GMT

​ 其告诉浏览器可以使用该组件的缓存,直到2019年4月7日上午8时30分15秒。

Max-Age和mod_expires

​ HTTP1.1中引入了Cache-Control来克服Expires头的限制。Expires头使用一个特定的时间,它要求服务器和客户端的时间严格同步(当然,可以通过Apache mode_expires模块中的ExpiresDefault以相对方式设置日期);另外,过期日期需要经常检查,一旦到达过去日期还需要在服务器端配置中提供一个新的日期。

Cache-Control: max-age=秒数指定组件缓存多久(下述,缓存1年)。

==Response Headers==
Cache-Control: max-age=31536000

max-age可以消除Expires的限制,但对于不支持HTTP1.1的浏览器,可以同时设置二者。二者同时存在时,HTTP规定max-age指令将重写Expires头。

修订文件名

​ 如果我们将组件配置可以在浏览器端进行缓存,当这些组件改变时用户如何获得更新呢?设置了Expires头时,过期前会一直使用缓存版本(从硬盘上读取组件),浏览器不会更新。为了确保用户能够获取组件的最新版本,需要在所有HTML页面修改组件的文件名。常用方式是增加MD5戳。

规则4:配置ETag

实体标签(Entity Tag,ETag)是Web服务器和浏览器用于确认缓存组件有效性的一种机制。

​ 浏览器下载组件后,会进行缓存,再次使用该组件时,会根据Expires头的值,判断是否发起请求。如果过期了,浏览器在重用之前必须检查他是否仍然有效,发送条件GET请求(前面已经提及)。如果是有效的,服务器会返回“304 Not Modified”,不会返回整个组件;这比简单地下载所有过期的组件效率要高。

服务器在检测缓存组件是否和原始服务器上的组件匹配时有两种方式:

  • 比较最新修改日期 (If-Modified-Since ==> Last-Modified)
  • 比较实体标签 ()

最新修改日期

原始服务器通过Last-Modified响应头来返回组件最新修改日期。

==Request Headers==
GET /1/4/A/1_ligang2585116.jpg
Host: avatar.csdn.net

==Response Headers==
HTTP 1.1 200 OK
Last-Modified: Wed, 11 Nov 2015 20:24:15 GMT
Content-Length: 19613

下一次请求http://avatar.csdn.net/1/4/A/1_ligang2585116.jpg时,浏览器会使用If-Modified-Since头将最新修改日期传回到原始服务器以进行比较。如果服务器上组件的最新修改日期与浏览器传回的值匹配,返回304,不会传送19613字节的数据。

==Request Headers==
GET /1/4/A/1_ligang2585116.jpg
Host: avatar.csdn.net
If-Modified-Since: Wed, 11 Nov 2015 20:24:15 GMT

==Response Headers==
HTTP 1.1 304 Not Modified

比较实体标签

ETag在HTTP1.1中引入,ETag是唯一标识了一个组件的一个特定版本的字符串。

==Request Headers==
GET /1/4/A/1_ligang2585116.jpg
Host: avatar.csdn.net

==Response Headers==
HTTP 1.1 200 OK
Last-Modified: Wed, 11 Nov 2015 20:24:15 GMT
ETag: "8224274EB79860E83F60346E0EEBE99A"
Content-Length: 19613

ETag的加入为验证实体提供了比较新修改日期更灵活的机制。例如,如果实体依据User-Agent或Accept-Language头而改变,实体的状态可以反映在ETag中。浏览器会使用If-None-Match头将ETag传回原始服务器以进行比较。如果服务器上组件的ETag值与浏览器传回的值匹配,返回304,不会传送19613字节的数据。

==Request Headers==
GET /1/4/A/1_ligang2585116.jpg
Host: avatar.csdn.net
If-Modified-Since: Wed, 11 Nov 2015 20:24:15 GMT
If-None-Match: "8224274EB79860E83F60346E0EEBE99A"

==Response Headers==
HTTP 1.1 304 Not Modified

ETag带来的问题

​ ETag通常使用组件的某些属性构造而成,这些属性对应特定的、寄宿了网站服务器来说是唯一的。当浏览器从一台服务器上获取了原始组件,之后,又向另外一台不同的服务器发送提交GET请求,ETag是不会匹配的–这对于服务器集群来处理请求的网站很常见,大大降低有效验证的成功率。

If-None-MatchIf-Modified-Since具有更高的优先级。你可能希望如果ETag不匹配但最新修改时间相同,也能发送一个“304 Not Modified”响应,但实际并不是这样的。HTTP1.1规范https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4,如果请求中同时出现了这两个头,则原始服务器“禁止(MUST NOT)”返回304,除非请求中的条件头字段全部一致。

ETag—用还是不用

​ 上面描述了ETag对于集群式网站的严重问题。但你可能会说,利用长久Expires头,使组件更长时间缓存到客户端,但是一旦用户点击了Reload或Refresh按钮,依然会产生条件GET请求。所以,你可以定义ETag只保留大小和时间戳作为内容,或者直接移除ETag。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue基础:组件--组件及组件通信

    组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML...

    奋飛
  • js提取主域及获取当前时区

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • npm模块管理器

    npm不需要单独安装。在安装node的时候,会连带一起安装npm。但是,node附带的npm可能不是最新版本,最好用下面的命令,更新到最新版本。

    奋飛
  • GeekPwn 2020云靶场挑战赛报名倒计时1天!欢迎顶尖“云上CTFer”踢馆

    7月11-12日,第二届GeekPwn云靶场挑战赛线上热身赛战火将重燃“云上”,再过1天就将失去了报名机会!你还不赶紧行动报名?

    腾讯安全
  • ROS与PCL中点云数据之间的转换

    应小伙伴们后台留言,想要了解ROS中如何使用PCL,本篇文章就将具体介绍一下。文章中如有错误,欢迎留言指出。也期待大家能够积极分享和讨论。

    点云PCL博主
  • 如何在ROS中使用PCL—数据格式(1)

    关于PCL在ros的数据的结构,具体的介绍可查 看 wiki.ros.org/pcl/Overview

    点云PCL博主
  • PCL点云分割(2)

    关于点云的分割算是我想做的机械臂抓取中十分重要的俄一部分,所以首先学习如果使用点云库处理我用kinect获取的点云的数据,本例程也是我自己慢慢修改程序并结合官方...

    点云PCL博主
  • PCL几种采样方法

    一般下采样是通过构造一个三维体素栅格,然后在每个体素内用体素内的所有点的重心近似显示体素中的其他点,这样体素内所有点就用一个重心点来表示,进行下采样的来达到滤波...

    点云PCL博主
  • Learning ROS for Robotics Programming Second Edition学习笔记(七) indigo PCL xtion pro live

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865

    zhangrelay
  • PCL点云曲面重建(1)

    在测量较小的数据时会产生一些误差,这些误差所造成的不规则数据如果直接拿来曲面重建的话,会使得重建的曲面不光滑或者有漏洞,可以采用对数据重采样来解决这样问题,通过...

    点云PCL博主

扫码关注云+社区

领取腾讯云代金券