前端加速与优化
提升网页性能与访问速度的优化点有许多,可大致将其分为“开发前”,“开发中”及“开发后”三个阶段的优化,本文整理了一些网页在开发完成后的一些有效提升页面性能与访问速度的优化点。主要侧重于前端,也会涉及到一些与服务端相关的配置项,本文中示例的配置不一定都能适用于你的项目,但基本的优化原理都是相通的,所以配置也大都大同小异,可根据你的实际情况自行搜索相应的解决方案。
缩减资源(HTML、CSS和JavaScript)的大小
缩减资源大小是指删除不必要的字节(例如,不必要的空格、换行符、缩进和注释)。压缩HTML、CSS和JavaScript可提高下载、解析和执行的速度。此外,对于CSS和JavaScript,还可将那些不会经常改动的公用资源合并成一个文件(例如,将jquery.js与bootstrap.js等文件合并成一个combined.js),以减少HTTP请求的次数,提升加载速度。可以通过以下方式进一步缩小文件体积:
- 要缩减HTML的大小,可使用HTML Minifier等类似的工具对HTML文件进行压缩,如果你使用自动化工具,如Gulp,可以使用gulp-htmlmin等插件进行压缩。
- 要缩减CSS的大小,可使用YUI Compressor和cssmin.js等工具,或使用Gulp的gulp-csso等插件进行压缩。
- 要缩减JavaScript的大小,可使用Google Closure Compiler和UglifyJS等工具,同样可以使用Gulp的gulp-uglify等插件进行压缩。
- 最后可以使用Gulp的gulp-concat或gulp-useref等合并插件对CSS和JavaScript进行文件合并处理。
优化图片
尽量减小图片尺寸,以缩减用户等待资源加载的时间。适当地设置图片的格式并进行压缩可以节省大量的数据字节空间。这样可以为那些网络连接较慢的用户节约时间,还可以为有流量套餐限制的用户节省成本。
推荐对所有图片进行基本优化和高级优化。基本优化包括裁剪不必要的区域,将颜色深度降至可接受的最低水平,移除图片评论以及将图片保存为恰当的格式。你可以使用任意图片编辑程序(例如,GIMP)执行基本优化。高级优化包括对JPEG和PNG文件执行进一步的压缩(无损压缩)。
使用图片压缩工具
有许多工具可用来对JPEG和PNG文件执行进一步的无损压缩,且不会对图片质量造成任何影响。对于JPEG文件,建议使用jpegtran或jpegoptim(仅适用于Linux;使用 --strip-all选项运行)。对于PNG文件,建议使用OptiPNG或PNGOUT工具,也可以使用Gulp的gulp-imagemin类似的插件进行自动化压缩。
选择恰当的图片文件格式
你应测试一下哪种格式最适合你的图片,尽管下面推荐了一些较高级别的格式:
- PNG格式几乎一直优于GIF格式,尽管某些旧版浏览器只能为PNG格式提供部分支持。
- 为较小或简单的图形(例如,小于10×10像素的图形或调色板小于3色的图形)以及包含动画的图片使用GIF格式。
- 为所有摄影风格的图片使用JPG格式。
- 请勿使用BMP格式或TIFF格式。
尽可能考虑使用图标字体
如果网页中使用了大量的小图标或修饰类的图片元素,应使用CSS Sprite技术将这些图片元素合并到一个单独的PNG文件中,以优化图片大小和减少HTTP请求。如果可以的话(由网页的设计风格决定),推荐尽可能考虑使用图标字体方案,以优化图标资源的大小以及在一些高分辨率的移动设备上的显示效果,同时还能提高图标资源在使用中的灵活性。这里推荐一个比较成熟的图标字体库库:Font Awesome。
首屏加载优化
如果所需的数据量超出初始拥塞窗口(Congestion Window)的限制,系统就需要在服务器和用户浏览器之间进行更多次的往返。如果用户使用的是延迟时间较长的网络(例如,移动网络),该问题会严重延迟网页的加载。
结构化HTML,以便首先加载关键的首屏内容
应考虑首先加载网页的主要内容。结构化网页,以便服务器发出的初始响应能发送必要数据,从而迅速呈现网页的关键部分并暂缓呈现其余部分。如果可能,你应该将CSS拆分为两个部分:页面的主要内容(例如,文章、产品和描述内容),以及可暂缓呈现的部分(例如,评论、广告和第三方小部件)。可以参考以下示例,了解有关如何结构化网站以提高加载速度:
- 如果你的HTML先加载第三方小部件,再加载主要内容,应将该加载顺序更改为先加载主要内容。
- 如果你的网站采用的是两列布局(如文章加侧边栏),而HTML先加载边栏,再加载文章,应考虑首先加载文章。
优化CSS的渲染过程
屏幕显示内容之前,浏览器会阻止外部CSS文件。这会导致额外的网络延迟,并延长屏幕显示内容的时间。
如果外部CSS资源较小,你可以直接将这些内容插入到HTML文档中,这称为“内嵌”。通过这种方式内嵌较小CSS资源,浏览器可以继续呈现网页。请注意,如果CSS文件较大,完全内嵌CSS可能会导致网页的首屏部分体积过大。如果CSS文件较大,你需要识别和内嵌呈现首屏内容所需的CSS,并暂缓加载其余样式,直到首屏内容显示之后为止。下面是一个简单的示例:
内嵌较小CSS文件的示例
如果HTML文档如下所示:
1 2 3 4 5 6 7 8 9 10 | <html> <head> <link rel="stylesheet" href="small.css"> </head> <body> <div class="blue"> Hello, world! </div> </body> </html> |
并且 small.css资源如下所示:
1 2 3 4 | .yellow {background-color: yellow;} .blue {color: blue;} .big {font-size: 8em;} .bold {font-weight: bold;} |
你就可以内嵌关键的CSS,具体方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <html> <head> <style> .blue {color: blue;} </style> </head> <body> <div class="blue"> Hello, world! </div> </body> </html> <link rel="stylesheet" href="small.css"> |
在网页加载之后,原始 small.css才会加载。然后你可以通过JavaScript将所有 <style>和 <link>元素插入到文档中,以维护CSS规则的应用顺序。
避免内嵌较大的Data URI
在CSS文件中内嵌Data URI时,请务必慎重。你可以在CSS中选择使用较小Data URI,因为内嵌较大Data URI会导致首屏CSS变大,进而延缓网页呈现时间。
避免内嵌CSS属性
应尽量避免在HTML元素(例如, <p style="color:blue;">)中内嵌CSS属性,因为这经常会导致出现多余的重复代码。此外,内容安全政策(CSP)会在默认情况下阻止在HTML元素上内嵌CSS。
移除阻止呈现的JavaScript
浏览器必须先解析网页,然后才能将其呈现给用户。如果浏览器在解析过程中遇到系统阻止的外部脚本,必须停止解析并且下载该JavaScript。每次遇到这种情况时,浏览器都会增加一个网络往返过程,这样就会导致首次呈现网页的时间延迟。
建议以内嵌方式处理呈现首屏区域所需的JavaScript,并让为网页添加其他功能所需的JavaScript延迟加载,直到首屏内容发送完毕为止。请注意,要通过这种方式缩短加载时间,你还必须优化CSS的渲染过程。
内嵌较小的JavaScript
如果外部脚本较小,你可以直接将它们添加到HTML文档。通过这种方式内嵌较小文件可让浏览器继续呈现网页。例如,如果HTML文档如下所示:
1 2 3 4 5 6 7 8 9 10 | <html> <head> <script type="text/javascript" src="small.js"></script> </head> <body> <div> Hello, world! </div> </body> </html> |
资源 small.js如下所示:
1 | /* contents of a small JavaScript file */ |
那么,你即可按如下这样内嵌脚本:
1 2 3 4 5 6 7 8 9 10 11 12 | <html> <head> <script type="text/javascript"> /* contents of a small JavaScript file */ </script> </head> <body> <div> Hello, world! </div> </body> </html> |
这样,你就可以将 small.js内嵌在HTML文档中,从而消除对它的外部请求。
异步加载JavaScript
为防止JavaScript阻止网页加载,建议你在加载JavaScript时使用HTML异步属性。例如:
1 | <script async src="my.js"></script> |
如果你的JavaScript资源使用的是 document.write,则使用异步加载就会不安全。建议你重写使用 document.write的脚本,以改用其他技术。
启用Gzip压缩功能
大多数网络服务器都可以通过调用第三方模块或使用内置程序将文件压缩为gzip格式,然后再发送该压缩文件以供下载。这样可以在下载呈现网页所需的资源时,可以节省一些时间。
建议你应该在自己的网络服务器上启用压缩功能。以下这些参考链接展示了一些常用的网络服务器上如何启用压缩功能:
- Apache:使用mod_deflate
- Nginx:使用HttpGzipModule
- IIS:配置HTTP压缩功能
- Node.js:使用compression中间件
避免目标网页重定向
由于重定向会触发额外的HTTP请求响应周期,并会额外延长往返时间延迟,因此,将应用发出的重定向数量降至最低至关重要。避免HTTP重定向可以缩减用户等待网页加载的时间。
以下是重定向模式的一些优劣示例:
- 优质:example.com使用响应式网页设计,无需重定向
- 中等:example.com -> m.example.com/home
- 劣质:example.com -> www.example.com -> m.example.com -> m.example.com/home
一些建议
如果你的网页需要针对桌面版与移动版浏览器提供不同的展现方式,建议优先使用响应式网页设计,自然就可以避免网页重定向了。
如果你的网页明确要求进行重定向,你应该执行以下两项操作:
- 使用HTTP重定向将使用移动版浏览器的用户直接发送到对应的移动版网址,而不执行任何中间的重定向;
- 并且在你的桌面版网页中加入 <link rel="alternate">标记来识别对应的移动版网址,以便搜索引擎“蜘蛛程序”能够找到你的移动版网页。
使用浏览器缓存
如果用户会多次访问你的网站,那么静态资源的浏览器缓存可以节省用户的时间。缓存标头应当应用到所有可缓存的静态资源中,而不仅仅是应用到一小部分静态资源(例如,图片)中。可缓存的资源包括JS和CSS文件、图像文件及其他二进制对象文件(媒体文件和PDF文件等)。通常情况下,HTML不是静态资源,默认情况下不应被视为可缓存资源。你应考虑哪些缓存策略适用于你的网站。
为你的服务器启用浏览器缓存。静态资源应该至少有一周的缓存有效期。广告或小部件这类的第三方资源也应该至少有一天的缓存有效期。对于所有可缓存资源,建议进行以下设置:
- 将 Expires设为将来日期,至少为一周,最多为一年(推荐优先设置 Expires,而不设置 Cache-Control: max-age,因为前者受支持的范围更为广泛)。应避免将其设为超过一年的将来日期,因为这样就违反了RFC准则。
- 如果你知道资源将具体在何时发生变化,则可以设置较短的过期日期。然而,如果你认为资源“可能将要发生变化”,但又不知道具体时间,则应设置较长的过期日期,并在资源文件名中使用文件指纹(下面会讲到)。
Expires和Cache-Control: max-age标头
这些标头用于指定相应时间段,浏览器可在指定的这段时间内使用已缓存的资源,而无需查看网络服务器是否提供了新版资源。这些缓存标头功能强大,没有任何应用条件限制。在设置这些标头并下载资源后,浏览器不会为资源发出任何GET请求,除非过期日期到期或达到时间最大值,亦或是用户清除了缓存。
Last-Modifed和ETag标头
这些标头可用于指定浏览器应如何确定用于缓存的文件是否相同。在 Last-Modified标头中指定的是日期,而在 ETag标头中指定的则可以是唯一标识资源的任意值(通常为文件版本或内容哈希值)。 Last-Modified是功能“较弱”的缓存标头,因为浏览器会使用试探法来确定是否需要从缓存中抓取内容。
借助这些标头,浏览器可以通过在用户明确重新加载页面时发出条件式GET请求,有效地更新其已缓存资源。除非你在服务器端更改资源,否则条件式GET请求不会返回完整的响应,因此相较于完整GET请求,此类请求的延迟较小。
应该使用哪个缓存标头?
对于所有可缓存资源,指定一个 Expires或 Cache-Control: max-age以及一个 Last-Modified或 ETag至关重要。你没必要同时指定 Expires和 Cache-Control: max-age,或同时指定 Last-Modified和 ETag。下列代码示例了如何在Nginx中为静态资源配置浏览器缓存:
1 2 3 4 5 6 | location ~ \.(css|js|png|jpg|jpeg|gif|bmp|webp|svg|xml|json|mp3|wav|mp4|pdf|swf|zip)$ { # 设置相关静态资源过期和缓存时间为一年 expires 31536000s; add_header Pragma "public"; add_header Cache-Control "max-age=31536000, public"; } |
使用文件指纹
对于偶尔发生变化的资源,我们可以让浏览器缓存相应的资源,直到该资源在服务器上出现变化,而服务器则在此时通知浏览器有新版本可用。我们可以通过为每个版本的资源指定一个唯一网址来实现这一目的。例如,假定我们有一个名为 my_stylesheet.css的资源。我们可以将文件重命名为 my_stylesheet_40dfc26.css。当资源发生变化时,其指纹就会发生变化,对应的网址也会随之更改。网址一经更改,系统就会强制浏览器重新抓取资源。通过指纹,我们甚至可以为变化更为频繁的资源设置一个最大的过期日期。
指纹识别的常用方法是使用对文件内容的哈希值进行编码的128位十六进制数。你可以使用Gulp自动化工具的gulp-rev和gulp-rev-replace等相关插件进行自动化添加文件指纹。
另一个策略是直接为新版应用创建新版目录,然后为版本目录中的各个版本放置所有资源。这样做的缺点是,如果各个版本中的资源未发生变化,则其网址将仍会更改以强制重新下载。使用内容哈希值不会遇到该问题,但这种方法稍微复杂一些。
优化网络请求
上一节中讲述了如何通过使用浏览器缓存来提升网页的访问速度,而缓存的前提是用户之前已经访问过你的网页,在浏览器中存在资源的缓存版本,且缓存尚未过期。但如果用户是第一次访问你的网页,或浏览器中缓存的资源版本已过期,需要更新资源时,这时浏览器需要通过网络请求和下载网页中的资源。所以针对网络请求阶段的优化对提升网页的访问速度是非常必要的。
减少DNS查询
DNS(Domain Name System,域名系统)查询是有成本的,通常需要20到120毫秒来查找主机名的IP地址。在查找完成之前,浏览器无法从主机下载任何内容。每次DNS查询都会增加对主机的初始请求的延迟。向大量不同的主机发出请求可能会降低性能。如果你的网页中使用了较多的来自不同主机的资源,应考虑在你的网页中使用DNS预解析技术,可有效的提升解析速度,你只需在网页的头部中添加如下示例代码:
1 | <link rel="dns-prefetch" href="http://img.example.com" /> |
具体的地址由你网页中实际所使用的地址决定,多个地址添加多行如上代码即可。更多详情可阅读DNS预解析技术这篇文档。
使用cookie-free的独立域名
下列代码示例了如何在Nginx中为静态资源设置cookie-free:
1 2 3 4 5 6 | location ~ \.(css|js|png|jpg|jpeg|gif|bmp|webp|svg|xml|json|mp3|wav|mp4|pdf|swf|zip)$ { # 为相关静态资源设置cookie-free fastcgi_hide_header Set-Cookie; tcp_nodelay off; break; } |
至于如何将资源部署到独立域名上也很简单,可根据你服务器的实际情况自行搜索解决方案。另外还有一种性能更高的解决方案是将你的静态资源部署到CDN上(下面会讲到)。
使用CDN加速
内容分发网络(Content Delivery Network,简称:CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。网页访问者与Web服务器的距离对响应时间是有影响的。在多个地理位置分散的服务器上部署内容,可以从用户的角度更快地加载网页。网站流量激增时,用户请求量、下行流量带宽增高,服务器压力大,站点响应慢。CDN通过全球部署的诸多节点及智能调度系统,让访问者可以就近访问海量静态资源。为你的全球用户提供同样快速的网络体验。
提供CDN服务的服务商有许多,有免费的如Cloudflare CDN、百度云加速和七牛云等。收费的如MaxCDN和阿里云CDN等,可根据你的实际需求自行选择。
以上就是我所整理和总结的一些前端加速与优化的解决方案,希望能对你有所帮助。
(本文完)
发表评论
想参与讨论?在下面编辑你的看法吧!(HTML标签部分可用)