前端资源比较庞大,包括HTML
、CSS
、JavaScript
、Image
、Flash
、Media
、Font
、Doc
等等,前端优化相对比较复杂,对于各种资源的优化都有不同的方式,按粒度大致可以分为两类,第一类是页面级别的优化,例如减小HTTP
请求数、脚本的无阻塞加载、内联脚本的位置优化等,第二类则是代码级别的优化,例如JavaScript
中的DOM
操作优化、图片优化以及HTML
结构优化等等。在用户角度前端优化可以让页面加载得更快,对用户的操作响应得更及时,能够给用户提供更为友好的体验,在服务商角度前端优化能够减少页面请求数,减小请求所占带宽,能够节省服务器资源。
加载前端的大部分时间在于下载各种资源,浏览器对于同一个服务器的HTTP
请求连接池数量也是有限的,对于过多的请求需要排队等候,最小化HTTP
请求减少请求次数可以防止HTTP
连接池被占满,同时也能避免过多HTTP
链接时的TCP
握手造成的时间消耗。
CSS Sprite
也就是俗称的雪碧图,将多张图片合并到一张图片中,可以减少图片的数量,此外由于合并图片相对分开的图片减少了存储信息的开销如颜色表和格式信息等,合并图片后的大小比分开的图片的大小的总和要趋于更小,当然如果合并图片时有大量空白来分隔原来的单个图片那么其大小会趋于更大。使用雪碧图,需要使用CSS
的background-image
和background-position
属性显示所需的图像段。
假如网站有很多带链接的图片例如地图应用等,那么图片映射image maps
将是一个很好的选择,image maps
允许在单张图片上有很多带链接的图片,通过<map>
与<area>
来将一张完整的图片映射分割为多个区域来制作不同的链接,同样是可以减少图片的HTTP
请求链接数量。
通过使用data:URL
方案来直接将图像数据嵌入到页面或者CSS
中,虽然这会增加文档或者是CSS
文件的大小,但同样这确实是一个减少HTTP
请求数量的方案,对于data:URL
的格式为data:[<mediatype>][;base64],<data>
。
使用字体图标来代替图标,将多个图标合成为字体图标不仅可以减少对于图片的HTTP
请求数量与图标大小,还作为矢量图对于放大缩小等操作不会失真,此外字体图标的优点还包括其很容易改变颜色、产生阴影、透明效果等,可以得到CSS
的很好的支持从而制作各种样式、旋转和动画效果等。
Combined files
也就是合并文件,将多个CSS
文件或者JavaScript
文件合并成一个CSS
文件或者JavaScript
文件,可以有效减少HTTP
请求数量,并且可以通过压缩算法减小文件的大小。当脚本和样式表在页面之间变化时,组合文件可能会变得难以阅读和修改,但是将其作为发布过程的一部分可以缩短响应时间。
通过服务器端设置响应头的Expires
与Cache-Control
来设置资源组件过期时间以及过期策略,对于静态资源可以通过设置Expires
为一个长期时间来实现永不过期策略,对于动态组件通过Cache-Control
指定缓存机制来辅助浏览器处理条件请求。
将JavaScript
与CSS
设置为外部文件引入而不是直接嵌入到HTML
中,由于浏览器的缓存机制,外部文件可以通过浏览器的缓存引入而不需要每次请求重复请求同一个资源文件,这样就使得浏览器在第二次打开页面的速度会快得多,当然全部由外部文件引入的方式会增加HTTP
请求数量,所以外部引用的关键问题就在于如何权衡相对于HTML
文档数量而言,缓存外部JavaScript
与CSS
文件的数量,尽管难以量化,但可以使用各种度量标准来衡量此因素,网站上的用户每个会话具有多个页面视图,并且许多页面都重复使用相同的脚本和样式表,则缓存的外部文件会带来更大的潜在利益。
根据浏览器渲染的顺序,将CSS
在<head>
中引入或者嵌入,相对于将CSS
放到<body>
或者页面底部来说,可以使页面渲染速度加快,这对于页面内容比较丰富的网站或者网络链接较慢时相当重要。假如将样式表放置于底部,就会导致浏览器还未加载样式表就开始渲染页面,无法渐进式渲染页面而直接从无样式状态立即跳转到有样式状态,用户体验较差;此外有些浏览器可能会在CSS
下载完成后才开始渲染页面,样式表放在下方会导致页面渲染推迟。
浏览器是可以并发请求的,这一特点使得其能够更快的加载资源,然而外部引入JavaScript
脚本在加载时却会阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载,原因之一是Js
可能会改变页面或者改变Js
间的依赖关系,例如A.js
中用document.write
改变页面,B.js
依赖A.js
。因此要严格保证顺序,不能并行下载。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。此外当浏览器发现Js
脚本时浏览器会立即开始解析脚本,并停止解析文档,因为脚本有可能会改动DOM
与CSS
,继续解析会浪费资源。解决这些问题的方法有很多例如异步加载脚本等,而最简单可依赖的方法就是将脚本尽可能的往后挪,减少对并发下载与页面渲染的影响。
CSS
表达式通过expression
方法来接受JavaScript
表达式,是一种动态设置CSS
的强大的方式,但同样也是非常危险的方式,CSS
表达式的问题在于其会进行频繁的计算,CSS
计算的频率要远远超出我们的想象,不仅在页面显示和缩放时会进行计算,在页面滚动或者移动鼠标都会重新计算一次,从而影响到页面的性能。可以通过使用Js
将属性进行一次算来并赋值给样式属性,也就是一次性表达式,如果必须在页面的整个生命周期中动态设置样式属性,则可以使用事件处理程序代替CSS
表达式。如果必须使用CSS
表达式,需要注意它们可能会被计算数千次,并且很可能会影响页面的性能。
尽量避免使用重定向,当页面发生了重定向,就会延迟整个HTML
文档的传输。在HTML
文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载,降低了用户体验。如果一定要使用重定向,如http
重定向到https
,要使用301
永久重定向,而不是302
临时重定向。因为如果使用302
,则每一次访问http
,都会被重定向到https
的页面,而永久重定向,在第一次从http
重定向到https
之后就会被浏览器记住,每次访问http
,会直接返回https
的页面。
JavaScript
操作DOM
无可避免的会触发浏览器的重绘或者回流,由于重绘和回流可能代价比较昂贵,因此最好就是可以减少它的发生次数,为了减少发生次数,我们可以合并多次对DOM
和样式的修改,然后一次处理掉,或者将样式事先设计好,动态去改变class
。或者采用离线修改DOM
的方案,使用documentFragment
对象在内存里操作DOM
,在内存中的DOM
修改就是让元素脱离文档流,当然是不会触发重绘的,将对DOM
的所有修改批量完成,想怎么改就怎么改,然后将节点再放入文档流中,只触发一次回流。
从HTTP / 1.1
开始,客户端可以通过使用HTTP
请求中的Accept-Encoding: gzip, deflate
来指示对压缩的支持。如果服务器在请求中看到此标头,则可以使用客户端列出的方法之一压缩响应,服务器通过响应中的Content-Encoding: gzip
通知客户端采用gzip
压缩。Gzip
的压缩率很高,是目前最流行,最有效的压缩方法,它由GNU
项目开发,并由RFC 1952
标准化。
压缩JavaScript
和CSS
文件,从代码中删除不必要的字符以减小其大小,从而缩短加载时间,当代码最小化时,所有注释以及不需要的空白字符都将被删除,由于减小了下载文件的大小,因此可以提高响应时间性能。
CDN
的全称是Content Delivery Network
,即内容分发网络,CDN
是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。当用户处于跨地域的多个位置时,对于服务器响应速度的感知是有差别的,用户访问网站的绝大部分时间都是处于下载静态资源状态的,将这些静态资源首先分发到CDN
各服务器,可以大大缩短响应时间,CDN
可以根据用户网络状态信息来选择网络跳数最少的服务器或响应最快的服务器来就近交予用户资源。
当浏览器访问一个域名的时候,需要解析一次DNS,获得对应域名的ip
地址。在解析过程中,按照浏览器缓存、系统缓存、路由器缓存、ISP
(运营商)DNS
缓存、根域名服务器、顶级域名服务器、主域名服务器的顺序,逐步读取缓存,直到拿到IP
地址,DNS Prefetch
,即DNS
预解析就是根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS
解析时间,来提高网站的访问速度
提前解析DNS
,由于它是并行的,不会堵塞页面渲染,这样可以缩短资源加载的时间
实体标签ETag
是Web
服务器和浏览器用来确定浏览器缓存中的资源是否与原始服务器上的资源匹配的一种机制,添加了ETag
,以提供一种比上次修改日期更灵活的验证实体的机制。ETag
是唯一标识组件特定版本的字符串,唯一的格式限制是用引号引起来,原始服务器使用ETag
响应头指定组件的ETag
。
当用户请求页面时,后端服务器将HTML
页面拼接在一起可能需要200
到500
毫秒的时间,在这段时间内,浏览器在等待数据到达时处于空闲状态,这段时间则可以将服务端部分已经处理好的数据发送到前端,使浏览器先开始加载外部资源。例如使用PHP
,则可以使用函数flush()
将部分就绪的HTML
响应发送到浏览器,以便浏览器可以在后端忙于处理HTML
页面的其余部分时开始获取资源,好处主要体现在繁忙的后端或轻量级前端。