- js效能優化是前端面試一個常問知識點。所以我結合我個人一些經驗,加上看了其他一些文章點分析。作了一些總結。
一,主要從使用者輸入網址到頁面渲染完成。及與使用者互動到方面來分析。
過程:
- 使用者輸入網址請求資源 => 將請求到的資源渲染成頁面 => 渲染完成的頁面互動 => 其他方面的優化
一,使用者輸入網址請求資源
- 當使用者輸入一個域名網址時,瀏覽器會將這個域名傳送到DNS伺服器
(這一過程中,我們可以通過改變我們host的配置,將域名配置為我們開發環境的伺服器ip。實現遠端開發); - 通過DNS伺服器進行域名解析,拿到對應的伺服器ip地址;
- 瀏覽器拿到伺服器的ip後,找到對應的伺服器;
- =>這個過程可以做個http快取優化(見下文——http快取)
- 獲取伺服器js,html等資源;
二,將請求到的資源渲染成頁面
- =>http頁面載入及渲染過程的優化(見下文——頁面載入及渲染過程)
三,渲染完成的頁面互動
- =>頁面互動過程的優化(見下文——頁面互動過程的優化)
四,其他方面的優化
- =>其他方面的優化的優化(見下文——其他方面的優化)
二,http快取及優化
- 強制快取
不會向伺服器傳送請求,直接從快取中讀取資源,
在chrome控制檯的network選項中可以看到該請求返回200的狀態碼,
並且size顯示from disk cache或from memory cache;
複製程式碼
設定強制快取的方式:
在響應頭設定:
Catch-Control優先順序 > Expires優先順序
Catch-Control:
比如Cache-Control:max-age=300(秒)
代表在這個請求正確返回時間(瀏覽器也會記錄下來)的5分鐘內再次載入資源,就會命中強快取
Expires:
比如Expires:Thu,21 Jan 2020 23:39:02 GMT
response header裡的過期時間,瀏覽器再次載入資源時,如果在這個過期時間內,則命中強快取。
它的值為一個絕對時間的GMT格式的時間字串;
存在一個缺點:但由於服務端時間和客戶端時間可能有誤差,這也將導致快取命中的誤差,因此現在常用這個;
複製程式碼
- 協商快取
協商快取:向伺服器傳送請求,伺服器會根據這個請求的request
header的一些引數來判斷是否命中協商快取,如果命中,則返回304狀態碼並帶上新的response
header通知瀏覽器從快取中讀取資源;另外協商快取需要與cache-control共同使用。;
複製程式碼
設定協商快取的方式:
在響應頭設定:
Etag優先順序 > Last-Modified優先順序
Etag:
伺服器響應請求時,通過此欄位告訴瀏覽器當前資源在伺服器生成的唯一標識(生成規則由伺服器決定);
If-None-Match: 再次請求伺服器時,瀏覽器的請求報文頭部會包含此欄位,後面的值為在快取中獲取的標識。
伺服器接收到次報文後發現If-None-Match則與被請求資源的唯一標識進行對比:
a. 不同,說明資源被改動過,則響應整個資源內容,返回狀態碼200。
b. 相同,說明資源無心修改,則響應header,瀏覽器直接從快取中獲取資料資訊。返回狀態碼304.
c. 但是實際應用中由於Etag的計算是使用演算法來得出的,而演算法會佔用服務端計算的資源,
所有服務端的資源都是寶貴的,所以就很少使用Etag了。
Last-Modified: Fri, 22 Jul 2020 01:47:00 GMT
伺服器在響應請求時,會告訴瀏覽器資源的最後修改時間;
if-Modified-Since:
瀏覽器再次請求伺服器的時候,請求頭會包含此欄位,後面跟著在快取中獲得的最後修改時間。
服務端收到此請求頭發現有if-Modified-Since,則與被請求資源的最後修改時間進行對比,
如果一致則返回304和響應報文頭,瀏覽器只需要從快取中獲取資訊即可。
從字面上看,就是說:從某個時間節點算起,是否檔案被修改了:
a. 如果真的被修改:那麼開始傳輸響應一個整體,伺服器返回:200 OK
b. 如果沒有被修改:那麼只需傳輸響應header,伺服器返回:304 Not Modified
if-Unmodified-Since:
從字面上看, 就是說: 從某個時間點算起, 是否檔案沒有被修改
如果沒有被修改:則開始繼續傳送檔案: 伺服器返回: 200 OK
如果檔案被修改:則不傳輸,伺服器返回: 412 Precondition failed (預處理錯誤);
這兩個的區別是一個是修改了才下載一個是沒修改才下載。
Last-Modified 說好卻也不是特別好,因為如果在伺服器上,一個資源被修改了,但其實際內容根本沒發生改變,
會因為Last-Modified時間匹配不上而返回了整個實體給客戶端(即使客戶端快取裡有個一模一樣的資源)。
為了解決這個問題,HTTP1.1推出了Etag。
複製程式碼
-
強制快取優先順序 > 協商快取優先順序;
-
重新整理
使用者行為對瀏覽器快取的影響
1.位址列訪問,連結跳轉是正常使用者行為,將會觸發瀏覽器快取機制;
2.F5重新整理,瀏覽器會設定max-age=0,跳過強快取判斷,會進行協商快取判斷;
3.ctrl+F5重新整理,跳過強快取和協商快取,直接從伺服器拉取資源。
複製程式碼
- 快取的優點
a. 減少了冗餘的資料傳遞,節省寬頻流量
b. 減少了伺服器的負擔,大大提高了網站效能
c. 加快了客戶端載入網頁的速度 這也正是HTTP快取屬於客戶端快取的原因。
複製程式碼
三,頁面載入及渲染過程(HTML,Js,css等資源的載入與執行)
- 當瀏覽器在解析dom樹的時候,如果遇到script標籤。會阻塞其他任務的執行。直到script標籤載入,解析完成;
- 這個時候頁面會一片空白。使用者體驗很不好,可以使用以下做法;
- => A, 將script標籤放在body的最底部,保證js檔案最後載入並執行;同時不影響頁面載入;
- => B, 或者使用defer,如下寫法。這種方式可以使瀏覽器解析到script標籤時,也會載入js。但他會等到dom樹解析完成後再去執行js(DomContentLoader之前執行),因此不會阻塞瀏覽器執行;
<script src="test.js" type="text/javascript" defer></script>
複製程式碼
- => C, 動態的插入script標籤來載入指令碼,比如通過以下程式碼(參考);
function loadScript(url, callback) {
const script = document.createElement('script');
script.type = 'text/javascript';
// 處理IE
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState === 'loaded' || script.readyState === 'complete') {
script.onreadystatechange = null;
callback();
}
}
} else {
// 處理其他瀏覽器的情況
script.onload = function () {
callback();
}
}
script.src = url;
document.body.append(script);
}
// 動態載入js
loadScript('file.js', function () {
console.log('載入完成');
});
複製程式碼
- 解析css時。不會操作html元素。因此不會阻塞dom樹的渲染;同時dom樹和css都解析完成後,合併生成渲染樹;
- 瀏覽器按照從上到下,從左到右的順序。繪製生成渲染樹。展示頁面給使用者;
四,渲染完成的頁面互動
- 滾動條呼叫介面時,可以用節流throttle等優化方式,減少http請求;
- 輸入搜尋時,可以用防抖debounce等優化方式,減少http請求;
- 減少dom元素對操作次數(參考);
// 優化前
const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優化後,一次性修改樣式,這樣可以將三次重排減少到一次重排
const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'
複製程式碼
- 使用事件委託;
- 應該儘可能的減少物件成員的查詢次數和巢狀深度;
- 降低迴圈遍歷次數(特別對於多維陣列的匹配時);
let arr1 = [{id: 12, name: 'aa'}, {id: 33, name: 'aa'}];// => {12: {id: 12, name: 'aa', 33: {id: 12, name: 'aa'}};
let arr2 = [{id: 12, age: '18'}, {id: 33,age: '22' }];
複製程式碼
五,其他方面的優化
- 對js,css等資源進行原始碼壓縮,減小檔案大小;
- 合理控制圖片大小,對長期固定圖片使用base64編碼,使用雪碧圖等;
- 設定樣式時,儘量使用className,少用style;
- 少用全域性變數,避免全域性搜尋;
- 方便時多使用原生方法;
- 使用第三方資源時。儘量做到按需載入;