年前,读完了《高性能站点建设指南》,但是一直没有整理。年后回来和同事一块儿出了份前端面试题,涉及到了关于性能优化的问题,在此特梳理一下。css
大量的公司在开发功能业务时,仅仅关注功能点的实现,对于性能方面要求很是低甚至不做为考虑范围。在遇到一些性能瓶颈时,也每每经过加机器的暴力方式去减缓,但那并不是解决这个问题的根本。做为前端project师。大部分人为了迎合需求一直在学习JavaScript、CSS、HTML5及Node。很是少去关注性能方面的东西。然而,有些性能的优化点仅仅需要花费很是少的时间和精力就能换来巨大的改善用户体验。html
在陈述前端性能优化的问题以前,咱们先思考例如如下问题:前端
一个页面从输入 URL 到页面载入显示完毕。这个过程当中都发生了什么?git
IP
地址,包括 DNS
详细的查找过程,包括:浏览器缓存、系统缓存、路由器缓存等;web
server发送一个 HTTP
请求;http://example.com
到 http://www.example.com
);HTTP
响应;HTML
;HTML
中的资源(如图片、音频、视频、CSS
、JS
等等);參考地址:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/web
性能黄金法则:仅仅有10%-20%的终于用户响应时间花在了下载HTML文档上。其他的80%-90%时间花在了下载页面中的所有组件上。面试
在阐述优化点以前。有必要的先说明一下HTTP。其做为浏览器和server之间的一种传输协议。在整个过程当中的做用相当重要。对HTTP不少其它的了解,推荐阅读《HTTP权威指南》。浏览器
压缩响应是最卓有成效的优化方式,浏览器可以使用Accept-Encoding头来声明支持压。server使用Content-Encoding头确认响应已被压缩。缓存
==Request Headers== Accept-Encoding:gzip ==Response Headers== Content-Encoding:gzip
假设浏览器在其缓存中保留了组件的一个副本。但并不肯定它是否仍然有效。就会生成一个条件Get请求。假设确认缓存在副本仍然有效,浏览器就可以使用缓存中的副本。ruby
典型状况下,缓存副本的有效性源自其最后改动时间。基于响应中的Last-Modified头,浏览器可以知道组件最后的改动时间。性能优化
它会使用If-Modified-Since头将最后改动时间发送给server。
假设组件没有被改动过。server会返回一个“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
条件GET请求和304响应有助于让页面载入得更快,但仍需要在client和server之间进行一次往返确认。以运行有效性检查。Expires头明白指出浏览器可否够使用组件的缓存副本。假设组件没有过时,浏览器就会使用缓存版本号而不会进行不论什么HTTP请求。
==Response Headers== Expires:Thu, 07 Apr 2019 08:30:15 GMT
HTTP构建在传输控制协议TCP(Transmission Control Protocol)之上。HTTP早期实现中。每个HTTP请求都要打开一个socket链接。
持久链接可以确保在单独的链接上进行多个请求。浏览器和server使用Connection头来指明对Keep-Alive的支持。
在HTTP1.1中并不是必须的,HTTP1.1中定义的管道可以在一个单独的socket上发送多个请求,管道性能优于持久链接。
但IE7不支持,因此很是多浏览器和server仍然包括Keep-Alive。
==Request Headers== Connection:keep-alive ==Response Headers== Connection:keep-alive
性能黄金法则中提到80%~90%时间花在HTML文档中组件下载。所以。下降组件的数量,并由此下降HTTP请求的数量。
是改善响应时间的最简单途径。
对于“图片超连接”的状况,可以使用图片地图下降页面图片个数,从而下降HTTP请求。
其分为server端图片地图和client图片地图。详见:HTML5-嵌入内容
同图片地图,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等工具。这里再也不赘述。需要思考的是:一个多页面的站点会有大量的模块,而模块的组合状况复杂,怎样合并模块值得花时间去分析一下本身的页面。确保组合的数量是可管理的。
内容公布网路(CDN)是一组分布在多个不一样地理位置的Webserver,用于更加有效地向用户公布内容,其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的情况。提升用户訪问站点的响应速度。使用CDN。需要注意:更新内容后,CDN的生效时间!
Expires头在前面已经阐述过,其目的主要是最大化利用浏览器的缓存来改善页面的性能。
浏览器(和代理)使用缓存来下降HTTP请求的数量,并下降HTTP响应的大小。
==Response Headers== Expires:Thu, 07 Apr 2019 08:30:15 GMT
其告诉浏览器可以使用该组件的缓存。直到2019年4月7日上午8时30分15秒。
HTTP1.1中引入了Cache-Control
来克服Expires
头的限制。
Expires
头使用一个特定的时间。它要求server和client的时间严格同步(固然,可以经过Apache mode_expires模块中的ExpiresDefault以相对方式设置日期);另外,过时日期需要常常检查。一旦到达过去日期还需要在server端配置中提供一个新的日期。
Cache-Control: max-age=秒数
指定组件缓存多久(下述,缓存1年)。
==Response Headers== Cache-Control: max-age=31536000
max-age
可以消除Expires的限制,但对于不支持HTTP1.1的浏览器。可以同一时候设置两者。两者同一时候存在时,HTTP规定max-age指令将重写Expires头。
假设咱们将组件配置可以在浏览器端进行缓存,当这些组件改变时用户怎样得到更新呢?设置了Expires头时。过时前会一直使用缓存版本号(从硬盘上读取组件),浏览器不会更新。为了确保用户可以获取组件的最新版本号,需要在所有HTML页面改动组件的文件名称。常用方式是添加MD5戳。
实体标签(Entity Tag。ETag)是Webserver和浏览器用于确认缓存组件有效性的一种机制。
浏览器下载组件后,会进行缓存。再次使用该组件时,会根据Expires头的值,推断是否发起请求。假设过时了。浏览器在重用以前必须检查他是否仍然有效,发送条件GET请求(前面已经说起)。假设是有效的。server会返回“304 Not Modified”,不会返回整个组件;这比简单地下载所有过时的组件效率要高。
server在检測缓存组件是否和原始server上的组件匹配时有两种方式:
原始server经过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头将最新改动日期传回到原始server以进行比較。假设server上组件的最新改动日期与浏览器传回的值匹配,返回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传回原始server以进行比較。假设server上组件的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一般使用组件的某些属性构造而成,这些属性相应特定的、寄宿了站点server来讲是惟一的。当浏览器从一台server上获取了原始组件。以后。又向另一台不一样的server发送提交GET请求,ETag是不会匹配的–这对于server集群来处理请求的站点非常常见,大大下降有效验证的成功率。
If-None-Match比If-Modified-Since具备更高的优先级。你可能但愿假设ETag不匹配但最新改动时间一样,也能发送一个“304 Not Modified”响应,但实际并不是这种。
HTTP1.1规范https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4,假设请求中同一时候出现了这两个头,则原始server“禁止(MUST NOT)”返回304。除非请求中的条件头字段所有一致。
上面描写叙述了ETag对于集群式站点的严重问题。但你可能会说,利用长久Expires头。使组件更长时间缓存到client。但是一旦用户点击了Reload或Refreshbutton。依旧会产生条件GET请求。
因此,你可以定义ETag仅仅保留大小和时间戳做为内容,或者直接移除ETag。