如何從請(qǐng)求、傳輸、渲染3個(gè)方面提升Web前端性能
什么是WEB前端呢?就是用戶電腦的瀏覽器所做的一切事情。我們來(lái)看看用戶訪問(wèn)網(wǎng)站,瀏覽器都做了哪些事情:
輸入網(wǎng)址 –> 解析域名 -> 請(qǐng)求頁(yè)面 -> 解析頁(yè)面并發(fā)送頁(yè)面中的資源請(qǐng)求 -> 渲染資源 -> 輸出頁(yè)面 -> 監(jiān)聽(tīng)用戶操作 -> 重新渲染。
通過(guò)上面的路徑可以看出瀏覽器分為請(qǐng)求、傳輸、渲染三部分來(lái)實(shí)現(xiàn)用戶的訪問(wèn),本文就從這三個(gè)部分來(lái)淺析如何提升WEB前端性能。
一、請(qǐng)求
瀏覽器為了減少請(qǐng)求傳輸,實(shí)現(xiàn)了自己的緩存機(jī)制。瀏覽器緩存就是把一個(gè)已經(jīng)請(qǐng)求過(guò)的Web資源拷貝一份副本存儲(chǔ)在瀏覽器中,當(dāng)再次請(qǐng)求相同的URL時(shí),先去查看緩存,如果有本地緩存,瀏覽器緩存機(jī)制會(huì)根據(jù)驗(yàn)證機(jī)制(Etag)和過(guò)期機(jī)制(Last-Modified)進(jìn)行判斷是使用緩存,還是從服務(wù)器傳輸資源文件。具體流程如下圖所示:
瀏覽器的請(qǐng)求有些是并發(fā)的,有些是阻塞的,比如:圖片、CSS、接口的請(qǐng)求是并發(fā);JS文件是阻塞的。請(qǐng)求JS的時(shí)候,瀏覽器會(huì)中斷渲染進(jìn)程,等待JS文件加載解析完畢,再重新渲染。所以要把JS文件放在頁(yè)面的最后。
JS也可以通過(guò)兩種方式由阻塞改成并行:一種是通過(guò)創(chuàng)建script標(biāo)簽,插入DOM中;另一種是在Script標(biāo)簽中增加async屬性。
每種瀏覽器對(duì)同一域名并發(fā)的數(shù)量有限制,IE6/7是2,IE9是10,其他常見(jiàn)的瀏覽器是6,所以減少資源請(qǐng)求數(shù)量和使用多域名配置資源文件,能大大提高網(wǎng)站性能。
減少資源請(qǐng)求數(shù)量的方法,大致有以下幾種:
1、通過(guò)打包工具,合并資源,減少資源數(shù)量。就是開(kāi)發(fā)版本是很多個(gè)資源文件,部署的時(shí)候,按類合并成幾個(gè)文件來(lái)輸出。在實(shí)現(xiàn)模塊管理的同時(shí),實(shí)現(xiàn)統(tǒng)一輸出。
2、CSS中,使用css sprite減少圖片請(qǐng)求數(shù)量。
3、通過(guò)延遲加載技術(shù),在用戶無(wú)感知的情況下請(qǐng)求資源。
4、通過(guò)服務(wù)器配置,實(shí)現(xiàn)一次請(qǐng)求,返回多個(gè)資源文件,如淘寶CDN那樣。
除了減少請(qǐng)求數(shù)量,也可以使用CDN鏡像,來(lái)減少網(wǎng)絡(luò)節(jié)點(diǎn),實(shí)現(xiàn)快速響應(yīng)。使用了CDN的請(qǐng)求,會(huì)根據(jù)用戶所處的地理位置,找尋最近的CDN節(jié)點(diǎn),如果請(qǐng)求是新的,則從資源服務(wù)器拷貝到節(jié)點(diǎn),然后再返回給客戶端。如果請(qǐng)求已經(jīng)存在,則直接從節(jié)點(diǎn)返回客戶端。
通過(guò)上面我們了解的緩存機(jī)制,如果我們部署上線的時(shí)候,是需要刷新緩存的。普通緩存通過(guò)強(qiáng)刷就能改過(guò)來(lái),而CDN緩存則需要通過(guò)改變URL來(lái)實(shí)現(xiàn)。同時(shí)我們不可能要求用戶按著Ctrl來(lái)刷新,所以通過(guò)打包工具,在部署的時(shí)候,統(tǒng)一更改URL是最有效的方式。而不常變更的庫(kù)文件,比如echart、jquery,則不建議更改。
二、傳輸
從服務(wù)器往客戶端傳輸,可以開(kāi)啟gzip壓縮來(lái)提高傳輸效率。
Gzip有從1-10的十個(gè)等級(jí)。越高壓縮的越小,但壓縮使用的服務(wù)器硬件資源就越多。根據(jù)實(shí)踐,等級(jí)為5的時(shí)候最均衡,此時(shí)壓縮效果是100k可以壓縮成20k。
三、渲染
瀏覽器在加載了html后,就會(huì)一邊解析,一邊根據(jù)解析出來(lái)的結(jié)果進(jìn)行資源請(qǐng)求,并生成DOM樹(shù)。而加載完畢的CSS,則被渲染引擎根據(jù)生成好的DOM樹(shù),來(lái)生成渲染樹(shù)。等所有資源解析完畢計(jì)算好layout后,向?yàn)g覽器界面繪制。隨著用戶操作,JS會(huì)修改DOM節(jié)點(diǎn)或樣式,重新繪制和重新排列。重新繪制指的是繪制DOM節(jié)點(diǎn)對(duì)應(yīng)的渲染節(jié)點(diǎn),重新排列是指重新計(jì)算這些節(jié)點(diǎn)在瀏覽器界面的位置。很顯然,重排是非常耗性能的。我們要做的是減少重排的次數(shù)。
生成DOM樹(shù)的時(shí)候,我們可以通過(guò)減少DOM節(jié)點(diǎn)來(lái)優(yōu)化性能。最初都是用table布局,節(jié)點(diǎn)深度和數(shù)量相當(dāng)復(fù)雜,性能很差。同樣CSS作為層疊樣式表,層級(jí)也不可太深,不然遍歷的成本很高。另外CSS的expression屬性相當(dāng)耗性能,能不用則不用。動(dòng)畫(huà)效果能用CSS寫(xiě)的就不用JS寫(xiě),渲染引擎不一樣,性能損耗也不一樣。
上面說(shuō)的是解析渲染的過(guò)程,我們?cè)俳又f(shuō)說(shuō)用戶交互操作的過(guò)程。用戶操作就會(huì)導(dǎo)致重繪和重排,重排一定會(huì)引起重繪,而重繪不一定會(huì)引起重排。到底怎樣會(huì)引起重排呢?簡(jiǎn)單的定義,DOM結(jié)構(gòu)的變化,以及DOM樣式中幾何屬性的變化,就會(huì)導(dǎo)致重排。幾何屬性顧名思義,就是寬、高、邊框、外補(bǔ)丁、內(nèi)補(bǔ)丁等俗稱盒模型的屬性。同時(shí)還有offset之類的邊距屬性。
重排是最耗能的,減少重排的方法有:
1、如果需要多次改變DOM,則先在內(nèi)存中改變,最后一次性的插入到DOM中。
2、同上一條,如果多次改變樣式,合成一條,再插入DOM中。
3、由于position的值為absoute和fixed時(shí)候,是脫離文檔流的,操作此類DOM節(jié)點(diǎn),不會(huì)引起整頁(yè)重排。所以動(dòng)畫(huà)元素設(shè)置position使其脫離文檔流。
4、當(dāng)DOM節(jié)點(diǎn)的display等于none的時(shí)候,是不會(huì)存在于渲染樹(shù)的,所以如果有比較復(fù)雜的操作,先使其display等于none,等待所有操作完畢后,再將display設(shè)成block,這樣就只重排兩次。
5、獲取會(huì)導(dǎo)致重排的屬性值時(shí),存入變量,再次使用時(shí)就不會(huì)再次重排。獲取這些屬性會(huì)導(dǎo)致重排:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
以上就是瀏覽器如何把資源變成肉眼所見(jiàn)的頁(yè)面的,除了上述根據(jù)瀏覽器流程而總結(jié)出來(lái)的性能優(yōu)化,我們還需要看看javascript作為程序,需要的優(yōu)化。先來(lái)看看javascript的垃圾回收機(jī)制。
Javascript的引擎會(huì)在固定的時(shí)間間隔,將不再使用的局部變量注銷掉,釋放其所占的內(nèi)存。而閉包的存在,將使引用一直存在,無(wú)法被釋放掉。全局變量的生命周期直至瀏覽器卸載頁(yè)面才會(huì)結(jié)束。所以一般來(lái)講,內(nèi)存溢出就是由于全局變量的不釋放和閉包引起。為了防止內(nèi)存溢出,我們可以做的方法有:
1、業(yè)務(wù)代碼放在匿名立即執(zhí)行函數(shù)里面,執(zhí)行完畢會(huì)立即釋放掉。
2、少用全局變量,同時(shí)用完的變量手動(dòng)注銷掉。
3、使用回調(diào)來(lái)代替閉包訪問(wèn)內(nèi)部屬性
4、當(dāng)不可避免使用閉包時(shí),慎重的對(duì)待其中的細(xì)節(jié)。不用的時(shí)候注銷掉。
5、通過(guò)瀏覽器自帶的工具profiles,來(lái)檢查內(nèi)存活動(dòng)情況。如果是波浪型的,說(shuō)明正常。如果是傾斜式漸進(jìn)上漲的,說(shuō)明有內(nèi)存不會(huì)被釋放,需要檢查相應(yīng)的函數(shù)。
最后再說(shuō)一點(diǎn),函數(shù)里返回異步取的值,經(jīng)常有人這么:
- Var getList = function(){ $.ajax().then(function(data){
- Return data;
- }) };
- Var users = getList();
毫無(wú)疑問(wèn),由于函數(shù)內(nèi)的返回是異步的,所以返回只能是undefined,而不是想要的data。于是為了實(shí)現(xiàn)返回data,就把a(bǔ)jax的async屬性設(shè)置成了false,由異步改為同步,來(lái)獲取到data。然而最大的問(wèn)題來(lái)了,同步是會(huì)中斷渲染進(jìn)程的,也就是請(qǐng)求返回的等待中,整個(gè)頁(yè)面是卡死的,用戶操作也不會(huì)有響應(yīng)。這個(gè)問(wèn)題真正的解決方案是返回promise對(duì)象,而不是把異步改成同步。