本文首發(fā)于kmac007.me

資源壓縮合并,減少HTTP請(qǐng)求
由于HTTP是無狀態(tài)協(xié)議,意味著每次HTTP請(qǐng)求都需要建立通信鏈路、進(jìn)行數(shù)據(jù)傳輸,而在服務(wù)器端,每個(gè)HTTP請(qǐng)求都需要啟動(dòng)獨(dú)立的線程處理。這些通信和服務(wù)的開銷是很昂貴的,減少HTTP請(qǐng)求的數(shù)目可有效提高訪問性能。以下方法可以對(duì)資源進(jìn)行壓縮合并,減少HTTP請(qǐng)求:
- 合并CSS,并壓縮
- 合并JavaScript,并壓縮
- 圖片壓縮合并,通過CSS的操作偏移量顯示不同的圖片。(CSS Sprite,即俗稱:雪碧圖)
異步加載
異步加載的方式
- 動(dòng)態(tài)腳本加載
通過JS動(dòng)態(tài)的創(chuàng)建<script>標(biāo)簽來動(dòng)態(tài)加載js文件。 defer
<script src="./a.js" defer></script>
async
<script src="./a.js" async></script>
異步加載的區(qū)別
如果不設(shè)置defer或者async,那么瀏覽器在遇到<script>標(biāo)簽時(shí),文檔的解析會(huì)停止,不再構(gòu)建DOM,會(huì)導(dǎo)致頁面阻塞直到腳本加載完畢。這是非常不好的用戶體驗(yàn),因此我們一般把<script>標(biāo)簽放置在<body>標(biāo)簽的最尾部。而defer和async兩者同樣可以解決這個(gè)問題。下面是二者的區(qū)別:
-
defer是在HTML解析完之后才會(huì)執(zhí)行,如果是多個(gè),按照加載的順序依次執(zhí)行。 -
async是在加載完之后立即執(zhí)行,如果是多個(gè),執(zhí)行順序和加載順序無關(guān)。
瀏覽器緩存
緩存的分類
強(qiáng)緩存
Expires Expires:Sat, 13 May 2017 14:22:34 GMT
Cache-Control Cache-Control: max-age=3600
協(xié)商緩存
Last-Modified If-Modified-Since Last-Modified: Sat, 13 May 2017 14:22:34 GMT
Etag If-None-Match
對(duì)于緩存這個(gè)部分,打算再寫一篇文章來描述。
使用CDN
CDN的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。其基本思路是盡可能避開互聯(lián)網(wǎng)上有可能影響數(shù)據(jù)傳輸速度和穩(wěn)定性的瓶頸和環(huán)節(jié),使內(nèi)容傳輸?shù)母臁⒏€(wěn)定。通過在網(wǎng)絡(luò)各處放置節(jié)點(diǎn)服務(wù)器所構(gòu)成的在現(xiàn)有的互聯(lián)網(wǎng)基礎(chǔ)之上的一層智能虛擬網(wǎng)絡(luò),CDN系統(tǒng)能夠?qū)崟r(shí)地根據(jù)網(wǎng)絡(luò)流量和各節(jié)點(diǎn)的連接、負(fù)載狀況以及到用戶的距離和響應(yīng)時(shí)間等綜合信息將用戶的請(qǐng)求重新導(dǎo)向離用戶最近的服務(wù)節(jié)點(diǎn)上。其目的是使用戶可就近取得所需內(nèi)容,解決 Internet網(wǎng)絡(luò)擁擠的狀況,提高用戶訪問網(wǎng)站的響應(yīng)速度。
簡(jiǎn)單來說,CDN將內(nèi)容緩存到分布各地的CDN的節(jié)點(diǎn)上,根據(jù)用戶的訪問IP,使用戶可就近取得所需要的內(nèi)容,提高網(wǎng)絡(luò)響應(yīng)速度。
預(yù)解析DNS
DNS Prefetch,即DNS的預(yù)獲取,是前端優(yōu)化的一部分。一般來說,在前端優(yōu)化中與DNS有關(guān)的有兩點(diǎn);
- 減少DNS的解析次數(shù)
- DNS的預(yù)解析,即
DNS Prefetch
一次DNS解析一般要耗費(fèi)20-120毫秒,減少DNS解析事件和次數(shù)是個(gè)很好的優(yōu)化方式。默認(rèn)情況下瀏覽器會(huì)對(duì)頁面中和當(dāng)前域名不在同一域的域名進(jìn)行預(yù)解析,并且緩存結(jié)果,這就是隱式的DNS Prefetch。如果向?qū)撁嬷袥]有出現(xiàn)的域進(jìn)行預(yù)解析,那么就要使用顯式的DNS Prefetch。
淘寶首頁中的DNS Prefetch

使用方法:
DNS Prefetch應(yīng)該盡量放在網(wǎng)頁的前面,推薦放在<meta>后面,具體用法如下:
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" >
<link rel="dns-prefetch" >
<link rel="dns-prefetch" >
需要注意的是,雖然DNS Prefetch能夠加快頁面的解析速度,但是不能濫用。
如果需要禁止隱式的DNS Prefetch,可以使用以下的標(biāo)簽:
<meta http-equiv="x-dns-prefetch-control" content="off">
事件節(jié)流
比如,寫一個(gè)滾動(dòng)加載組件,監(jiān)聽scroll事件,這時(shí)每次滾動(dòng)都會(huì)執(zhí)行多次回調(diào)函數(shù),這是相當(dāng)消耗性能的。因此,我們可以通過事件節(jié)流的方式,減少滾動(dòng)回調(diào)函數(shù)的觸發(fā),從而提升性能。
例如:
var loadMore = document.getElementById('#loadMore')
var timer
//計(jì)時(shí)器的回調(diào)函數(shù)
function callback() {
//距離頂部的距離
const top = loadMore.getBoundingClientRect().top
//視口高度
const windowHeight = document.documentElement.clientHeight
if(top && top < windowHeight){
//loading...
}
}
//監(jiān)聽滾動(dòng)事件并對(duì)函數(shù)節(jié)流
window.addEventListener('scroll', ()=>{
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(callback, 100)
})
如上代碼,我們通過定時(shí)器的方式進(jìn)行函數(shù)節(jié)流,有效減少了回調(diào)函數(shù)的執(zhí)行。
減少DOM操作
對(duì)DOM操作的代價(jià)是高昂的,這在web應(yīng)用中通常是一個(gè)性能瓶頸。
在《高性能JavaScript》中這么比喻:“把DOM看成一個(gè)島嶼,把JavaScript(ECMAScript)看成另一個(gè)島嶼,兩者之間以一座收費(fèi)橋連接”。所以每次訪問DOM都會(huì)教一個(gè)過橋費(fèi),而訪問的次數(shù)越多,交的費(fèi)用也就越多。所以一般建議盡量減少過橋次數(shù)。
查詢和修改DOM元素會(huì)造成頁面的Repaint和Reflow。那么我們先了解下什么是Repaint和Reflow。
Repaint和Reflow
Repaint(重繪)就是在一個(gè)元素的外觀被改變,但沒有改變布局(寬高)的情況下發(fā)生,如改變visibility、outline、背景色等等。
Reflow(重排) 的成本比 Repaint 的成本高得多的多。DOM Tree 里的每個(gè)結(jié)點(diǎn)都會(huì)有 Reflow 方法,一個(gè)結(jié)點(diǎn)的 Reflow 很有可能導(dǎo)致子結(jié)點(diǎn),甚至父點(diǎn)以及同級(jí)結(jié)點(diǎn)的 Reflow。在一些高性能的電腦上也許還沒什么,但是如果 Reflow 發(fā)生在手機(jī)上,那么這個(gè)過程是非常痛苦和耗電的。
每次設(shè)置style屬性改變節(jié)點(diǎn)樣式,每設(shè)置一次都會(huì)導(dǎo)致一次reflow,所以最好通過設(shè)置class的方式; 有動(dòng)畫效果的元素,它的position屬性應(yīng)當(dāng)設(shè)為fixed或absolute,這樣不會(huì)影響其它元素的布局;如果功能需求上不能設(shè)置position為fixed或absolute,那么就權(quán)衡速度的平滑性。
總之,因?yàn)?Reflow 有時(shí)確實(shí)不可避免,所以只能盡可能限制Reflow的影響范圍。
緩存DOM查詢
// 未緩存 DOM 查詢
for(let i = 0; i < document.getElementsByTagName('p').length; i++) {
//...
}
上面這種情況下,每次循環(huán)都要進(jìn)行DOM查詢,非常影響性能。
我們通過如下的方式緩存DOM,這樣,就不需要每次都進(jìn)行DOM查詢,達(dá)到了減少DOM查詢的目的。
// 緩存了 DOM 查詢
var pList = document.getElementsByTagName('p')
for(let i = 0; i < pList.length; i++) {
//...
}
合并DOM插入
在循環(huán)插入DOM時(shí),我們可以將部分生成的DOM節(jié)點(diǎn)插入到一個(gè)片段中,最后統(tǒng)一將片段插入到HTML中。
var listNode = document.getElementById('list')
// 要插入10個(gè)li
var frag = document.createDocumentFragment()
var li
for(let i = 0; i < 10; i++) {
li = document.createElement('li')
li.innderHTML = "List item" + i
frag.appendChild(li)
}
listNode.appendChild(frag)
懶加載
懶加載的原理是通過自定義屬性標(biāo)簽存放圖片原有的src屬性,當(dāng)img標(biāo)簽出現(xiàn)在瀏覽器窗口范圍內(nèi)再依次將原src屬性填充以達(dá)到懶加載的效果。這種方法減少了開始加載網(wǎng)頁時(shí)的請(qǐng)求,減少瀏覽器卡死的幾率,減少了流量的消耗,同時(shí)提高了用戶體驗(yàn)。
主要步驟:
- 判斷圖片是否可見(滾動(dòng)高度 + 窗口高度 > 圖片到頁面頂部高度 && 圖片到頁面頂部高度 + 圖片高度 > 滾動(dòng)高度)
- 如果圖片可見,將存放在
data-src中原本的src屬性填充src屬性中。
以下為一個(gè)圖片懶加載的示例:
<script async src="http://jsfiddle.net/enpk4e92/embed/result,js,html,css/dark/"></script>
SSR服務(wù)端渲染
服務(wù)端渲染可以提高性能。React.js和Vue.js目前都支持服務(wù)端渲染。在此不做深究。