性能之前端篇
循環(huán)優(yōu)化
在多重嵌套循環(huán)的程序上,如果能分出出多個(gè)獨(dú)立循環(huán)也比嵌套在一個(gè)循環(huán)體內(nèi)來(lái)的更有益。
優(yōu)化循環(huán)的3種方式:減少每次迭代的開(kāi)銷(xiāo)、減少迭代的次數(shù)或者重新設(shè)計(jì)應(yīng)用程序。
在測(cè)試的時(shí)候僅可能模擬真實(shí)環(huán)境:如低端機(jī)器和低速網(wǎng)絡(luò)。

?
Ajax優(yōu)化
對(duì)于連續(xù)頁(yè)面之間的差別很小的應(yīng)用而言,使用Ajax技術(shù)能帶來(lái)顯著的改善。
?
減少重繪
在html頁(yè)面完成展現(xiàn)之后,動(dòng)態(tài)改變頁(yè)面元素或調(diào)整css樣式都會(huì)引起瀏覽器重繪,性能的損耗直接取決于動(dòng)態(tài)改變的范圍:如果只是改變一個(gè)元素的顏色之類(lèi)的信息則只會(huì)重繪該元素;而如果是增刪節(jié)點(diǎn)或調(diào)整節(jié)點(diǎn)位置則會(huì)引起其兄弟節(jié)點(diǎn)也一并重繪。
減少重繪并不是說(shuō)不要重繪,而是要注意重繪范圍:
- 改動(dòng)的DOM元素越深則影響越小,所以盡量深入節(jié)點(diǎn)改動(dòng);
- 對(duì)某些DOM樣式有多重變動(dòng)盡量合并到一起修改;
以改變一個(gè)<a>標(biāo)簽的背景色、寬度和顏色為例。
<a href="JAVAscript:void(0);" id="example">傳統(tǒng)的代碼</a> <script> var example = document.getElementById("example"); example.ondblclick = function() { example.style.backgroundColor = "red"; example.style.width = "200px"; example.style.color = "white"; } </script>
以上會(huì)執(zhí)行3次重繪,而通過(guò)CSS代替JavaScript多次執(zhí)行則只進(jìn)行一次重繪。
<style> .dblClick { width: 200px; background: red; color: white; } </style> <a href="javascript:;" id="example">CSS優(yōu)化的代碼</a> <script> var example = document.getElementById("example"); example.ondblclick = function() { example.className = "dblClick"; } </script>
避免腳本阻塞加載
當(dāng)瀏覽器在解析常規(guī)的script標(biāo)簽時(shí),它需要等待script下載完畢,再解析執(zhí)行,而后續(xù)的HTML代碼只能等待。CSS文件引入要放在頭部,因?yàn)檫@是HTML渲染必備元素。
為了避免阻塞加載,應(yīng)把腳本放到文檔的末尾,而CSS是需要放在頭部的!
<head> <link rel="stylesheet" href="common.css"> ...... <script src="example.js"></script> </body>
避免節(jié)點(diǎn)深層級(jí)嵌套
深層級(jí)嵌套的節(jié)點(diǎn)在初始化構(gòu)建時(shí)往往需要更多的內(nèi)存占用,并且在遍歷節(jié)點(diǎn)時(shí)也會(huì)更慢些,這與瀏覽器構(gòu)建DOM文檔的機(jī)制有關(guān)。瀏覽器會(huì)把整個(gè)HTML文檔的結(jié)構(gòu)存儲(chǔ)為DOM“樹(shù)”結(jié)構(gòu)。當(dāng)文檔節(jié)點(diǎn)的嵌套層次越深,構(gòu)建的DOM樹(shù)層次也會(huì)越深。
如下代碼,完全能夠去掉
或其中一個(gè)標(biāo)簽。<div> <span> <label>嵌套</label> </span> </div>
頁(yè)面緩存
通常不設(shè)置緩存的情況下,每次刷新頁(yè)面都會(huì)重新讀取服務(wù)器的文件,而如果設(shè)置緩存之后,所有文件都可以從本地取得,這樣明顯極大提高了頁(yè)面效率。
我們可以通過(guò)設(shè)置頁(yè)面頭的expires來(lái)定義頁(yè)面過(guò)期時(shí)間,將過(guò)期時(shí)間定久一點(diǎn)就達(dá)到了“永久”緩存。
<meta http-equiv="expires" content="Sunday 26 October 2099 01:00 GMT" />
當(dāng)然,如果你的項(xiàng)目代碼有變更,因?yàn)榭蛻舳司彺媪宋募偷貌坏阶钚碌奈募瑒?shì)必造成顯示錯(cuò)誤。基于這個(gè)問(wèn)題的解決方案就是給鏈接文件加一個(gè)時(shí)間戳,如果時(shí)間戳有變化,瀏覽器會(huì)認(rèn)為是新文件,就會(huì)向服務(wù)器請(qǐng)求最新文件。
<script src="example2014-6-17.js"></script> //如果是JSP,可以用EL表達(dá)式來(lái)取時(shí)間戳信息,這樣管理更加方便 <script src="example${your time param}.js"></script> //或者這樣的寫(xiě)法更優(yōu)秀: <script src="example.js?time=2014-6-7"></script> <script src="example.js?time=${your time param}"></script>
壓縮合并文件
所有涉及到請(qǐng)求數(shù)據(jù)的文件盡量做壓縮,比如Javascript文件、css文件及圖片文件,特別是圖片文件,如果沒(méi)有高清晰要求,完全可以壓縮后再使用。
數(shù)量少體積大的文件要比數(shù)量多體積小的文件加載速度快,所以有時(shí)候可以考慮將多個(gè)js文件、多個(gè)css文件合并在一起。
除此之外減少HTML文檔大小還可以采取下面幾種方法:
- 刪掉HTM文檔對(duì)執(zhí)行結(jié)果無(wú)影響的空格空行和注釋
- 避免Table布局
- 使用HTML5 ### HTML+CSS3+Javascript各司其職 讓三元素各司其職才能做出高性能的網(wǎng)頁(yè):HTML是頁(yè)面之本也是內(nèi)容之源,有了它就能跟CSS和Javascript交互;CSS3可以說(shuō)是展現(xiàn)大師,而且日漸強(qiáng)大的CSS能代替Javascript做很多動(dòng)態(tài)的事情如漸變、移動(dòng)等動(dòng)態(tài)效果;Javascript是動(dòng)態(tài)數(shù)據(jù)之王,舊瀏覽器依靠js來(lái)完成動(dòng)態(tài)效果展現(xiàn),但現(xiàn)在的CSS也能完成js的工作,所以盡量將工作交給css,這樣會(huì)獲得更好的性能。(這個(gè)說(shuō)得有點(diǎn)大)
圖像合并實(shí)現(xiàn)CSS Sprites
圖像合并其實(shí)就是把網(wǎng)頁(yè)中一些背景圖片整合到一張圖片文件中,再利用CSS 的“background-image”,“background- repeat”,“background-position”的組合進(jìn)行背景定位,background-position可以用數(shù)字能精確的定位出背景圖片的位置。
一個(gè)頁(yè)面要用到多個(gè)圖標(biāo),完全可以將多個(gè)圖標(biāo)合并成一個(gè)圖,然后只需要發(fā)一次圖片請(qǐng)求,通過(guò)css定位分割圖標(biāo)即可。
避免使用Iframe
使用iframe并不會(huì)增加同域名下的并行下載數(shù),瀏覽器對(duì)同域名的連接總是共享瀏覽器級(jí)別的連接池,在頁(yè)面加載過(guò)程中iframe元素還會(huì)阻塞父文檔onload事件的觸發(fā)。并且iframe是html標(biāo)簽中最消耗資源的標(biāo)簽,它的開(kāi)銷(xiāo)比DIV、SCRIPT、STYLE等DOM高1~2個(gè)數(shù)量級(jí)。
避免onload事件被阻塞,可使用JavaScript動(dòng)態(tài)的加載iframe元素或動(dòng)態(tài)設(shè)置iframe的src屬性(但其僅在高級(jí)瀏覽器IE9及以上有效)。
<iframe id="if"></iframe> document.getElementById("if").setAttribute("src","url");
多域名請(qǐng)求
一般來(lái)說(shuō),瀏覽器對(duì)于相同域名的圖片,最多用2-4個(gè)線程并行下載(不同瀏覽器的并發(fā)下載數(shù)是不同的)。而相同域名的其他圖片,則要等到其他圖片下載完后才會(huì)開(kāi)始下載。
有時(shí)候,圖片數(shù)據(jù)太多,一些公司的解決方法是將圖片數(shù)據(jù)分到多個(gè)域名的服務(wù)器上,這在一方面是將服務(wù)器的請(qǐng)求壓力分到多個(gè)硬件服務(wù)器上,另一方面,是利用了瀏覽器的特性。(大家可以去新浪、騰訊門(mén)戶網(wǎng)站查看,這些大型站點(diǎn)同一頁(yè)面加載的圖片可能由多個(gè)站點(diǎn)提供)
注:一個(gè)HTML請(qǐng)求的域名也不要太多(2~3個(gè)差不多),多了可能造成不同服務(wù)器連接時(shí)間差異,反而影響速度。
避免空鏈接屬性
如<img src=""><a href="">這樣的設(shè)置方式是非常不可取的,即使鏈接為空,在舊的瀏覽器也會(huì)以固定步驟發(fā)送請(qǐng)求信息。
另外<a href="#"></a>也不可取,最好的方式是在鏈接中加一個(gè)空的js代碼<a href="javascript:void();"></a>
使用圖像的BASE64編碼
base64是一串字符串,他可以代表一個(gè)圖片的所有信息,也就是可以通過(guò)
(S表示一串base64碼)來(lái)顯示圖片,這種方式不需要再向服務(wù)器發(fā)送請(qǐng)求,完全由瀏覽器解析成圖片。
目前高級(jí)瀏覽器都支持此功能,但要注意兩點(diǎn):
- 低版本瀏覽器(如IE7)不支持;
- base64字符串長(zhǎng)度隨圖片的大小及復(fù)雜度成正比,base64也像URL一樣,也有超出長(zhǎng)度的情況(在IE8下很常見(jiàn))。所以要根據(jù)情況來(lái)使用。
顯式設(shè)置圖片的寬高
如果HTML里的圖片沒(méi)有指定尺寸(寬和高),或者代碼描述的尺寸與實(shí)際圖片的尺寸不符時(shí),瀏覽器則要在圖片下載完成后再“回溯”該圖片并重新顯示,這會(huì)消耗額外時(shí)間。
<iframe id="if"></iframe> document.getElementById("if").setAttribute("src","url");
顯式指定文檔字符集
如果瀏覽器不能獲知頁(yè)面的編碼字符集,一般都會(huì)在執(zhí)行腳本和渲染頁(yè)面前,把字節(jié)流緩存,然后再搜索可進(jìn)行解析的字符集,或以默認(rèn)的字符集來(lái)解析頁(yè)面代碼,這會(huì)導(dǎo)致消耗不必要的時(shí)間。
<iframe id="if"></iframe> document.getElementById("if").setAttribute("src","url");
漸進(jìn)式增強(qiáng)設(shè)計(jì)
漸進(jìn)式增強(qiáng)設(shè)計(jì)的通俗解釋就是:首先寫(xiě)一段滿足所有瀏覽器的基本樣式,再在后面針對(duì)不同高級(jí)瀏覽器編寫(xiě)更漂亮的樣式
如下代碼,所有瀏覽器都支持background-color: #2067f5;滿足了瀏覽器基本現(xiàn)實(shí)需求,而后面的background-image: -webkit-gradient等則為不同高級(jí)瀏覽器使用,只要瀏覽器識(shí)別就能執(zhí)行這段代碼(不識(shí)別,CSS也不會(huì)報(bào)錯(cuò)只會(huì)直接忽略)。
<div class="someClass"></div> .someClass { width: 100px; height: 100px; background-color: #2067f5; background-image: -webkit-gradient(linear, left top, left bottom, from(#2067f5), to(#154096)); background-image: -webkit-linear-gradient(top, #2067f5, #154096); background-image: -moz-linear-gradient(top, #2067f5, #154096); background-image: -ms-linear-gradient(top, #2067f5, #154096); background-image: -o-linear-gradient(top, #2067f5, #154096); background-image: linear-gradient(to bottom, #2067f5, #154096); }
懶加載與預(yù)加載
預(yù)加載和懶加載,是一種改善用戶體驗(yàn)的策略,它實(shí)際上并不能提高程序性能,但是卻可以明顯改善用戶體驗(yàn)或減輕服務(wù)器壓力。
預(yù)加載表示當(dāng)前用戶在請(qǐng)求到需要的數(shù)據(jù)之后,頁(yè)面自動(dòng)預(yù)加載下一次用戶可能要看的數(shù)據(jù),這樣用戶下一次操作的時(shí)候就立刻呈現(xiàn),依次類(lèi)推。
懶加載表示用戶請(qǐng)求什么再顯示什么,如果一個(gè)請(qǐng)求要響應(yīng)的時(shí)間非常長(zhǎng),就不推薦懶加載。
Flush機(jī)制
當(dāng)一個(gè)頁(yè)面非常大,內(nèi)容非常多,可以采用flush的形式分部分返回給頁(yè)面,這樣能告訴用戶我正在工作,顯示一部分內(nèi)容比白屏等很長(zhǎng)時(shí)間要好得多。在Java Web技術(shù)中,實(shí)現(xiàn)Flush非常簡(jiǎn)單,只要調(diào)用 HttpServletResponse.getWriter輸出流的flush方法,就可以將已經(jīng)完成加載的內(nèi)容寫(xiě)回給客戶端。
這種方式只適用于返回?cái)?shù)據(jù)特別多、請(qǐng)求時(shí)間特別長(zhǎng)的情況,常規(guī)數(shù)據(jù)還是用正常的實(shí)時(shí)全部返回最佳。這種實(shí)現(xiàn)方式實(shí)際會(huì)增加瀏覽器渲染時(shí)間和用戶整體等待時(shí)間,但從用戶體驗(yàn)上會(huì)更加優(yōu)秀。
性能之服務(wù)器優(yōu)化
CDN機(jī)制
所謂的CDN,就是一種內(nèi)容分發(fā)網(wǎng)絡(luò),它采用智能路由和流量管理技術(shù),及時(shí)發(fā)現(xiàn)能夠給訪問(wèn)者提供最快響應(yīng)的加速節(jié)點(diǎn),并將訪問(wèn)者的請(qǐng)求導(dǎo)向到該加速節(jié)點(diǎn),由該加速節(jié)點(diǎn)提供內(nèi)容服務(wù)。
通俗點(diǎn)說(shuō),你在成都(瀏覽器)購(gòu)買(mǎi)了北京賣(mài)家(服務(wù)器)的產(chǎn)品,北京賣(mài)家通過(guò)快遞(CDN服務(wù))寄送包裹,從北京到成都可以走路、坐汽車(chē)、火車(chē)或飛機(jī),而采用CND的快遞會(huì)選擇飛機(jī)直達(dá),因?yàn)檫@種寄送方式最快。
當(dāng)然使用CDN有兩個(gè)注意事項(xiàng):
- CDN加速服務(wù)很貴,如果你覺(jué)得你的網(wǎng)站值得加速,可以選擇購(gòu)買(mǎi);
- CDN不適合局域性網(wǎng)站,比如你的網(wǎng)站只有某一個(gè)片區(qū)訪問(wèn)或者局域網(wǎng)訪問(wèn),因?yàn)閰^(qū)域性網(wǎng)絡(luò)本來(lái)就很近,無(wú)需CDN加速。
HTTP協(xié)議的合理使用
瀏覽器緩存帶來(lái)的性能提升已經(jīng)眾人皆知了,而很多人卻并不知道瀏覽器的緩存過(guò)期時(shí)間、緩存刪除、什么頁(yè)面可以緩存等,都可以由我們程序員來(lái)控制,只要您熟悉HTTP協(xié)議,就可以輕松的控制瀏覽器。
動(dòng)靜分離
所謂的動(dòng)靜分離,就是將Web應(yīng)用程序中靜態(tài)和動(dòng)態(tài)的內(nèi)容分別放在不同的Web服務(wù)器上,有針對(duì)性的處理動(dòng)態(tài)和靜態(tài)內(nèi)容,從而達(dá)到性能的提升。我們知道如果一個(gè)HTML有多個(gè)域名請(qǐng)求數(shù)據(jù)文件會(huì)提高
Tomcat服務(wù)器在處理靜態(tài)和并發(fā)問(wèn)題上比較弱,所以事先動(dòng)靜分離的方式一般會(huì)用Apache+Tomcat、Nginx+Tomcat等。
以Apache+Tomcat為例,其運(yùn)行機(jī)理是:頁(yè)面請(qǐng)求首先給Apache,然后Apache分析請(qǐng)求信息是靜態(tài)還是動(dòng)態(tài),靜態(tài)則本機(jī)處理,動(dòng)態(tài)則交給Tomcat做處理。
這其實(shí)是負(fù)載均衡的雛形,這樣的實(shí)現(xiàn)不用讓開(kāi)發(fā)人員做任何特殊開(kāi)發(fā),一個(gè)
交給服務(wù)器即可,至于這個(gè)文件是從Apache還是從Tomcat取得,開(kāi)發(fā)人員完全無(wú)需關(guān)注。
HTTP持久連接
持久連接(Keep-Alive)也叫做長(zhǎng)連接,它是一種TCP的連接方式,連接會(huì)被瀏覽器和服務(wù)器所緩存,在下次連接同一服務(wù)器時(shí),緩存的連接被重新使用。HTTP無(wú)狀態(tài)性表示了它不屬于長(zhǎng)連接,但HTTP/1.1提供了對(duì)長(zhǎng)連接的支持(不過(guò)這必須依賴瀏覽器和服務(wù)器雙方均支持長(zhǎng)連接功能才行),最常見(jiàn)的HTTP長(zhǎng)連接例子是“斷點(diǎn)下載”。
瀏覽器在請(qǐng)求的頭部添加 Connection:Keep-Alive,以此告訴服務(wù)器“我支持長(zhǎng)連接,你支持的話就和我建立長(zhǎng)連接吧”,而倘若服務(wù)器的確支持長(zhǎng)連接,那么就在響應(yīng)頭部添加“Connection:Keep-Alive”,從而告訴瀏覽器“我的確也支持,那我們建立長(zhǎng)連接吧”。服務(wù)器還可以通過(guò)Keep-Alive:timeout=..., max=...的頭部告訴瀏覽器長(zhǎng)連接失效時(shí)間。
配置長(zhǎng)連接通常是要服務(wù)器支持設(shè)置,有測(cè)試數(shù)據(jù)顯示,使用長(zhǎng)連接和不使用長(zhǎng)連接的性能對(duì)比,對(duì)于Tomcat配置的maxKeepAliveRequests為50來(lái)說(shuō),效率竟然提升了將近5倍。
GZIP壓縮技術(shù)
HTTP協(xié)議支持GZIP的壓縮格式,當(dāng)服務(wù)器返回的HTML信息報(bào)頭中包含Content-Encoding:gzip,它就告訴瀏覽器,這個(gè)響應(yīng)的返回?cái)?shù)據(jù)已經(jīng)壓縮成GZIP格式,瀏覽器獲得數(shù)據(jù)后要進(jìn)行解壓縮操作,一定程度上減輕了服務(wù)器傳輸數(shù)據(jù)的壓力。
很多服務(wù)器已經(jīng)支持通過(guò)配置來(lái)自動(dòng)將HTML信息壓縮成GZIP,比如tomcat、又比如很火的Nginx。如果無(wú)法配置服務(wù)器級(jí)別的GZIP壓縮機(jī)制,可以改為程序壓縮。
// 監(jiān)視對(duì) gzipCategory 文件夾的請(qǐng)求 @WebFilter(urlPatterns = { "/gzipCategory/*" }) public class GZIPFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String parameter = request.getParameter("gzip"); // 判斷是否包含了 Accept-Encoding 請(qǐng)求頭部 HttpServletRequest s = (HttpServletRequest)request; String header = s.getHeader("Accept-Encoding"); //"1".equals(parameter) 只是為了控制,如果傳入 gzip=1,才執(zhí)行壓縮,目的是測(cè)試用 if ("1".equals(parameter) && header != null && header.toLowerCase().contains("gzip")) { HttpServletResponse resp = (HttpServletResponse) response; final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); HttpServletResponseWrApper hsrw = new HttpServletResponseWrapper( resp) { @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding())); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public void write(int b) throws IOException { buffer.write(b); } }; } }; chain.doFilter(request, hsrw); byte[] gzipData = gzip(buffer.toByteArray()); resp.addHeader("Content-Encoding", "gzip"); resp.setContentLength(gzipData.length); ServletOutputStream output = response.getOutputStream(); output.write(gzipData); output.flush(); } else { chain.doFilter(request, response); } } // 用 GZIP 壓縮字節(jié)數(shù)組 private byte[] gzip(byte[] data) { ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(10240); GZIPOutputStream output = null; try { output = new GZIPOutputStream(byteOutput); output.write(data); } catch (IOException e) { } finally { try { output.close(); } catch (IOException e) { } } return byteOutput.toByteArray(); } …… }
如果你覺(jué)得本篇還不錯(cuò),請(qǐng)點(diǎn)贊關(guān)注!
文字由黑碼教主創(chuàng)作,配圖源于網(wǎng)絡(luò),版權(quán)歸原作者所有,如有侵權(quán)聯(lián)系刪除!
你有更好的網(wǎng)站性能優(yōu)化方案嗎?
歡迎留言分享。