現在許多公司往往注重后端優化,而忽略了前端優化
想想如果辛苦優化了服務器,后台,排查了sql
卻在最后頁面加載展示的時候很慢,也得不償失
其實,前后台優化都是相輔相成的
后台優化好了,響應請求速度快,前台展示的更迅速
前台優化了不必要的請求,后台壓力也會更小
一直想靜下心來寫一篇博文,
無奈心浮氣躁,不得安定,斷斷續續一直寫到今天....
那么現在就來和大家分享一下我的前端優化經驗吧
大綱:
請求優化
首先我們來優化HTTP請求數
由於用戶瀏覽的,往往只是局部網頁,
所以只加載用戶可視范圍內的資源,就會減少一些不必要的請求,也會減少瀏覽器加載資源的消耗
考慮到移動端可視范圍,網絡流量,性能,延遲加載作用尤為明顯
適合延遲加載的東西很多,最需要的當然是圖片,這里推薦lazyload
https://github.com/tuupola/jquery_lazyload
圖片延遲加載的原理就首先將要延遲加載的圖片src替換為空白圖片或者參數指定的loading圖
然后根據當前元素的位置(offset)來判斷是否在頁面可視范圍(頁面寬/高度+滾動寬/高度)
如果在,就將真實圖片資源路徑替換回src讓瀏覽器加載
防止瀏覽器解析到HTML中<img>標簽的src屬性就開始下載資源,最好將原<img>的src屬性去掉
統一配置lazyload的參數去加載loading圖吧,如我們項目中這樣:
$(".main_content img").lazyload({ placeholder: "/images/loading.gif", threshold:200 });
再來看到lazyload的源代碼,可視范圍判斷上下左右,寫的十分完善
$.belowthefold = function(element, settings) { var fold; if (settings.container === undefined || settings.container === window) { fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop(); } else { fold = $(settings.container).offset().top + $(settings.container).height(); } return fold <= $(element).offset().top - settings.threshold; }; $.rightoffold = function(element, settings) { var fold; if (settings.container === undefined || settings.container === window) { fold = $window.width() + $window.scrollLeft(); } else { fold = $(settings.container).offset().left + $(settings.container).width(); } return fold <= $(element).offset().left - settings.threshold; }; $.abovethetop = function(element, settings) { var fold; if (settings.container === undefined || settings.container === window) { fold = $window.scrollTop(); } else { fold = $(settings.container).offset().top; } return fold >= $(element).offset().top + settings.threshold + $(element).height(); }; $.leftofbegin = function(element, settings) { var fold; if (settings.container === undefined || settings.container === window) { fold = $window.scrollLeft(); } else { fold = $(settings.container).offset().left; } return fold >= $(element).offset().left + settings.threshold + $(element).width(); }; $.inviewport = function(element, settings) { return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) && !$.belowthefold(element, settings) && !$.abovethetop(element, settings); };
關於圖片這里,除了延遲加載外,用戶上傳的圖片以及我們所用的資源圖片都應該進行壓縮處理
如需要進一步提高壓縮率,可以使用例如:google開發的webp圖片格式等..
不過不是所有瀏覽器都支持webp格式,需要針對瀏覽器響應
根據上面這段代碼,其實我們就可以延遲加載其他內容了,
總之呢,這里我們的目的就是盡量減少不必要的請求
比如現在用的很多的下拉式翻頁,就是判斷到頁面底部之后再ajax獲取下頁內容
如果考慮到網頁只是局部更新的話,那使用ajax是很合適的
好處顯而易見,無需重新請求整頁,小巧快速,網頁展示也友好一些
善用ajax對前端性能,體驗都是有改善的
但是也要考慮到對搜索引擎的友好,
如果頁面整體功能改變了,或者頁面改動量大就要進行取舍了。
延遲加載的目的就是減少不必要的請求,在用戶有需求時才請求資源
所以對於用戶來說,其實是有一點點“等待”的過程的
一般會用loading圖,等待文字來改善這里的用戶體驗
但是有一些需求是希望盡量少出現這種“等待”過程的
這里我們就可以預加載資源,如下,我們先在js中加載圖片
var img = new Image(); img.src="test.png";
提前加載好了圖片,用戶進行下一步時,圖片則是從瀏覽器緩存中獲取
多頁數據也可以類似處理,頁面初始可以默認加載兩頁數據
翻到第二頁時,就ajax去獲取第三頁內容
翻到第三頁時,就去獲取第四頁內容......
總是提前預加載一頁數據
如此可一定程度上減少一些等待的過程
總的來說延遲加載是盡量少加載資源,預加載則是判斷可能要的資源,盡量去提前多加載,
都是為了優化用戶的體驗,適用於不同場景
資源優化
網頁中有些資源可以通過延遲加載等方式減少不必要的請求
而許多的javascript腳本,css樣式等資源卻是網頁中必須要加載的
既然不可避免需要加載,那接着,我們就可以去優化這些資源
首先我們可以優化資源文件的大小,資源文件變小了,傳輸速度當然也快了
這里我們壓縮分為如下兩種:
- HTTP壓縮,我們可以在web服務器內設置壓縮輸出的內容
如下,在IIS7內啟用Gzip壓縮
可以看到,設置了之后資源的HTTP響應多了Content-Encoding:gzip
- 對javascript代碼,css樣式代碼進行語法壓縮,減少文件大小
我們寫js代碼,css,都命名盡量取得有意義,多換行,tab把格式弄的優美
都是為了方便維護,再閱讀,而機器解析我們的代碼的時候可不管這些。
這里壓縮我推薦 uglify-js和 clean-css,都在nodejs環境下
這里可見壓縮效果還是很顯著的,按我目前代碼習慣,平均可以壓縮一半左右
但是,不可避免,我們在日常開發,發布的時候,會增加額外的工作量
這里我分享一下我的用法:
為了方便開發,我在項目中還是引用的原文件
只是在生成發布文件后,執行下面bat批處理腳本
把發布文件夾內的js代碼進行壓縮,同名覆蓋原文件,這樣就不用修改項目內js引用地址
@echo off :: 設置壓縮JS文件的根目錄,腳本會自動按樹層次查找和壓縮所有的JS SET JSFOLDER=D:\project\scripts echo 正在查找JS文件 chdir /d %JSFOLDER% for /r . %%a in (*.js) do ( @echo 正在壓縮 %%~a ... uglifyjs %%~fa -mangle -o %%~fa ) echo 完成! pause & exit
(*這個bat批處理腳本不是我寫的,是以前在網上搜來的)
當然,用這種bat批處理腳本只滿足一些小需求,我們還可以用自動化構建工具來生成
這里我推薦gulp,同樣在nodejs環境下,這里分享一個我自己寫的靜態資源壓縮生成后綴的gulpjs
裝好gulp之后,需要安裝gulp的插件gulp-clean-css,gulp-uglify,gulp-rev,gulp-rev-collector以及gulp-sync
還有這個gulp-bom,我之前處理完之后,老是有亂碼,發現生成的文件雖然是utf-8 卻不是utf-b+bom,所以需要再處理一下
var gulp = require('gulp'), config = require('./config'), cleancss = require("gulp-clean-css"), uglifyjs = require("gulp-uglify"), rename = require("gulp-rename"), rev = require("gulp-rev"), revCollector = require("gulp-rev-collector"), del = require("del"), sync = require("gulp-sync")(gulp), bom=require("gulp-bom") ; //生成后綴 gulp.task("addv_css", function () { return gulp.src(config.css.src) .pipe(rev()) .pipe(gulp.dest(config.css.dest)) .pipe(rev.manifest()) .pipe(bom()) .pipe(gulp.dest(config.css.rev)); }); gulp.task("addv_js", function () { return gulp.src(config.js.src) .pipe(rev()) .pipe(gulp.dest(config.js.dest)) .pipe(rev.manifest()) .pipe(bom()) .pipe(gulp.dest(config.js.rev)); }); //更改cshtml中的 style引用 gulp.task("changev_css", function () { return gulp.src(config.rev.csssrc) .pipe(revCollector({ replaceReved: true })) .pipe(bom()) .pipe(gulp.dest(config.rev.dest)); }); //更改cshtml中的 js引用 gulp.task("changev_js", function () { return gulp.src(config.rev.jssrc) .pipe(revCollector({ replaceReved: true })) .pipe(bom()) .pipe(gulp.dest(config.rev.dest)); }); //壓縮css gulp.task("cleancss", function () { return gulp.src(config.css.cleansrc) .pipe(cleancss()) .pipe(bom()) .pipe(gulp.dest(config.css.dest)); }); //壓縮js gulp.task("uglify", function () { return gulp.src(config.js.uglifysrc) .pipe(uglifyjs({ mangle: true, define: true })) .pipe(bom()) .pipe(gulp.dest(config.js.dest)); }); gulp.task("default", sync.sync([["addv_css", "addv_js"], "changev_css", "changev_js", ["cleancss", "uglify"]]));
我們項目內往往會引用多個javascript腳本,和多個css樣式文件
所以可以把多個腳本合並到一個js文件內,然后統一引用它就能減少http請求
這里uglify-js和 clean-css 都支持多個文件合並壓縮輸出
>uglifyjs js1.js js2.js -m -o merge.js
>cleancss -o megar.css style1.css style2.css
也可以在服務器內合並輸出,比如我們看淘寶的合並:
<script src="//g.alicdn.com/kissy/k/6.2.4/??node-min.js,node-base-min.js,dom-base-min.js,query-selector-base-min.js,dom-extra-min.js,node-event-min.js,event-dom-base-min.js,event-base-min.js,event-dom-extra-min.js,event-gesture-min.js,event-touch-min.js,node-anim-min.js,anim-transition-min.js,anim-base-min.js,promise-min.js,base-min.js,attribute-min.js,event-custom-min.js,json-base-min.js,event-min.js,io-min.js,io-extra-min.js,io-base-min.js,io-form-min.js,cookie-min.js"></script>
他們則是在web服務器內做了處理,請求多個文件,會自動合並
有條件的同學也可以這樣進行合並
圖片合並目的也是減少http請求,將頁面中需要的相關圖片合並到一張大圖片里
然后用css的background-position定位到大圖的具體局部位置
比如這樣:
但是近年來好像用的越來越少了
想想原因,應該是。。。。維護起來[太麻煩]了
只要稍微改動一張圖片,尤其是改變了大小的話,整個大圖片都要改
並且還要把css樣式也改個遍
否則就是繼續往大圖片里拼,無效的老圖保留,導致圖片越來越臃腫
再有,如果只是一些這樣的小圖標的話,用fonticon要方便得多
iconfont就是圖標字體,將圖標輸出為矢量的字體
使用起來非常方便,對應字體的編碼,就和網頁中的普通文字一樣
<i class="icon iconfont">U</i>
它對比合並的圖片來說
體積會更小,並且能用css控制大小,顏色
然后它還是矢量的,放大也不失真
雖然iconfont制作,維護成本也不低,引用起來為兼容瀏覽器得引用多種格式
但好在現在用的人多,網上也有許多免費圖標庫供使用
比如這里阿里巴巴圖標庫
引用css放在<head>內,引用js放在</body>結束標簽前,現在很多朋友都會這么做了
css加載是異步的,更早的加載出樣式就能更早呈現出頁面
js放在尾部,防止瀏覽器加載js而阻塞頁面,造成頁面“白屏”現象
如果有條件的話,我們還可以啟用額外的服務器,域名來存放資源
這樣能減少主域名的HTTP請求數,讓主服務器更快響應請求
還能減少主域名的cookie請求
緩存
說到緩存,首先想到的肯定是服務器后台的緩存
其實,打開我們的瀏覽器工具看一下,就會發現,網頁前台也存在着許多緩存
它無聲無息,悄悄優化着每一次訪問
直接看響應頭,我們會發現這些字段:
Expires:Thu, 20 Oct 2016 06:43:43 GMT //告訴瀏覽器此日期以前可以使用緩存文件
Cache-Control:public, max-age=3600 //表示資源在3600毫秒之內可以使用緩存文件,如果和Expires同時存在則覆蓋Expires
Last-Modified:Wed, 13 Jul 2015 08:52:12 GMT//配合Cache-Controls使用,標示資源的最后修改日期
ETag:5384183131862232576//配合Cache-Controls使用,標示資源由服務器生成的唯一標識(某些資源最后修改日期不可靠,或者不希望展示最后修改日期,就可以使用ETag)
上面這些都是控制前端緩存的字段,然后再來看看下面的HTTP狀態碼
//200 OK 請求已成功,請求所希望的響應頭或數據體將隨此響應返回。
//304 Not Modified 請求資源沒有改變,可以使用緩存資源
所以這里當我們第一次訪問的時候,得到響應資源及緩存策略,
之后如果緩存有效,資源就會來自緩存
如果緩存無效了,就會判斷是否有Last-Modified或者ETag,有則發送If-Modified-Since或If-None-Match請求頭
服務器如果判斷資源無修改,就會返回304,有修改就會返回200以及新的緩存策略
當然,這里只是指用戶的普通操作,如:地址欄回車,頁面鏈接跳轉,新窗口,前進后退
如果用戶是進行 刷新 操作,則不會讀取緩存資源了,但是如果有Last-Modified或者ETag,依然會發送If-Modified-Since或If-None-Match請求頭
如果用戶是進行 強制刷新(ctrl+f5) 操作,那所有緩存策略都失效,會重新請求
可見,我們往往只有第一次請求的時候,才會有較多的資源加載
所以我們上面做了這么多優化,延遲加載,資源壓縮,合並等等,
都是為了用戶第一次訪問的時候盡量友好。
那么Cache-Control要如何在服務器設置呢,其實針對靜態資源來說
大部分服務器都會默認就將Cache-Control設置好了的,我們可以視情況修改一些時效參數等等
我們這里要注意的是用url去控制緩存的有效性
如下:
雖然它們請求的都是服務器上的同一個js文件,但是瀏覽器不會把它們當作同一個資源
可以看到v=1.01.js的請求是來源於cache
其他幾個請求都是新請求
所以,我們每次發布都可以修改資源url強制讓用戶頁面上的緩存失效
這樣用戶不需要刷新也能得到最新的資源
離線存儲在我之前一篇文章里也提到過,在移動端應用的比較多
它和緩存不同,它設置好了之后,連離線也能訪問,無論用戶刷新或者新窗口,鏈接等等
使用manifest
<html manifest="/mobile.manifest">
在html上添加manifest,其中文件格式內容如:
CACHE MANIFEST ##需要離線的內容 CACHE: Script/jquery.js Script/gameconfig.js Image/home.png Image/logo.png ##總是訪問網絡的內容 NETWORK: * ##訪問A失敗時訪問B FALLBACK
瀏覽器將緩存chache內所有的內容,並且可以離線訪問,只要文件發生任何改變都將會重新讀取並刷新全部緩存,所以更改注釋是個更新緩存的好方法
這里要注意的是
1,添加了manifest的當前網頁也會被緩存 所以推薦的方式是頁面緩存,頁面動態內容全部用ajax獲取,所以在移動網站項目設計開始就要注意這個問題
2,頁面中添加iframe 然后子頁面引用manifest想達到緩存資源而不緩存當前頁面內容,是無效的。
本地存儲數據一直是網頁端的弱項,在沒有HTML5的localStorage前,用cookie可以保存一點數據
但付出的代價很大,cookie能保存的數據很少,並且它會伴隨着每一次請求一起發送
localStorage就好多了,默認5MB的大小,除非用戶手動清除,否則一直不過期,就連IE8瀏覽器都支持
這里要注意,localStorage和cookie一樣受到跨域的限制
可以使用domain控制
document.domain="";
其它的
在js中,我們實現動畫,就是利用定時器循環改變dom元素的屬性來達到動畫效果
但是許多屬性更改之后會造成瀏覽器重繪,增加性能消耗
當然瀏覽器更新換代也做了許多優化,我們優化js,css減少重繪,也能改進動畫性能
但是想一想,究竟應不應該讓js去實現頁面動畫呢?
css3就是往這方面發展,讓js更純粹的去實現業務邏輯
頁面效果之類的事情就讓css去做吧
並且css3在動畫效率上面也有增強,瀏覽器會單獨處理css3動畫,不占用js主線程,還可以硬件加速
將來還有提升的可能,所以快把我們的js動畫替換為css3吧!
同樣更迭的還有flash,當初flash是為了彌補網頁展現的不足而出現的“插件”
而現在網頁標准一次次升級,html5的出現,再加上flash自身也有各種漏洞,性能問題
尤其是現在flash在移動端的支持很少,都加快了我們替換flash的步伐
寫到這里快寫完了,突然發現好像自己在啰啰嗦嗦了一整頁。。
卻又感覺哪里漏都了一些,好吧,希望大家不要嫌煩,如果都了解了,就當溫習吧
如有紕漏,歡迎提醒
哦,還有些想說的
優化一定要選擇適合自己項目,硬件條件的優化方式,千萬不要盲目硬搬
還有要持續不斷的關注新標准,方法,一直保持活力!