原文刊登在github,若是掘金有圖片加載不出的,你們也能夠直接看原版 github地址javascript
在互聯網網站百花齊放的今天,網站響應速度是用戶體驗的第一要素,其重要性不言而喻,這裏有幾個關於響應時間的重要條件:css
用戶在瀏覽網頁時,不會注意到少於0.1秒的延遲;html
少於1秒的延遲不會中斷用戶的正常思惟, 可是一些延遲會被用戶注意到;前端
延遲時間少於10秒,用戶會繼續等待響應;java
延遲時間超過10秒後,用戶將會放棄並開始其餘操做;node
所以你們都開始注重性能優化,不少廠商都開始作一些性能優化。比較有名的是雅虎軍規,不過隨着瀏覽器和協議等的發展,有一些已經逐漸被淘汰了。所以建議你們以歷史的目光看待它。好比.儘可能減小HTTP請求數這一條,在HTTP2協議下就無論用了,由於HTTP2實現了HTTP複用,HTTP請求變少,反而下降性能。所以必定要結合歷史環境看待具體的優化原則和手段,不然會拔苗助長。react
雅虎軍規中文版: http://www.cnblogs.com/xianyulaodi/p/5755079.htmllinux
隨着移動互聯網的高速發展,移動終端的數量正在以指數級增加,不少廠商對於移動端體驗都開始重視起來了。好比Google Chrome 的工程師 Alex就提出了Progressive Web App(如下簡稱PWA),用來提升移動端web的性能。PWA的核心是Service Worker, 經過它可使得在JS主線程以外,程序員經過編程的方式控制網絡請求,結合瀏覽器自己的緩存,每每能夠達到很是棒的用戶體驗。PWA提出了許多相似Native的「功能」- 好比離線加載和桌面快捷方式,使得移動端web體驗更加友好。另外加上web端自己的特性-好比快速迭代,可索引(你能夠經過搜索引擎搜索,而native app 則不行)等,使得更多的人投入到給web端用戶提供更佳的用戶體驗的PWA中去。Google在更早的時候,提出了AMP。 2017年Google dev上海站就宣傳了PWA 和 AMP,而且經過一張動圖形象地展現了二者(PWA的P和A翻過來,而後W上下翻轉就是AMP,反之亦然)。AMP是一種面向手機端的輕量級的web展示,經過將重量級元素從新實現等方式提升了手機端性能。 另外諸如使用asm.js 使得代碼更容易編譯成機器指令,也是性能優化的一環。若是你仔細查看應用執行的profile的時候,你會發現js代碼compile的時間會好久,尤爲你寫了不少無用js代碼,或者不必第一時間執行的代碼的時候,這種影響更加大。js代碼最終也是編譯成二進制給機器執行,而js是動態語言,也就是說js代碼是執行到哪編譯到哪,這是和java這樣的靜態語言的一個很大的差異。V8已經對這部分作了至關大的優化,通常狀況下咱們沒必要擔憂,一般來說這也不會成爲性能瓶頸,由於這些時間和網絡IO的時間根本不是一個數量級。可是在特定場合,提早編譯成更容易解釋執行的代碼,可能就會派上用場。git
在我剛剛接觸前端的時候,常常看到這樣的性能優化例子:程序員
// bad
for(var i = 0;i < data.length;i++){
// do something...
}
// good
for(var i = 0,len = data.length;i < len;i++){
// do something...
}
複製代碼
理由是上面的會每次去計算data.length。我的上面的優化很是好笑,且不說實際運行狀況怎樣。就算下面的性能比上面的好,我以爲這樣的性能優化應該交給編譯器來作,不該該交給上層業務去作,這樣作反而喪失了可理解性,你們很明顯的看出上面的更容易理解,不是嗎?
過早的優化,會讓人陷入心理誤區。這種心理誤區就是典型的手中有錘子,到處都是釘子。
還有一點就是若是過早優化,每每會一葉障目。性能優化要遵照木桶原理,即影響系統性能的永遠是系統的性能短板。若是過早優化,每每會頭痛醫腳,忙手忙腳卻毫無收效。
讓咱們回憶一下瀏覽器從加載url開始到頁面展現出來,通過了哪些步驟:
tips: 在chrome瀏覽器中, 能夠輸入 chrome://dns/ 查看chrome緩存的dns記錄
瀏覽器調用網絡模塊。 網絡模塊和目標IP 創建TCP鏈接,途中通過三次握手。
瀏覽器發送http請求,請求格式以下:
header
(空行)
body
複製代碼
請求到達目標機器,並經過端口與目標web server 創建鏈接。
web server 獲取到請求流,對請求流進行解析,而後通過一些列處理,可能會查詢數據庫等, 最終返回響應流到前端。
瀏覽器下載文檔(content download),並對文檔進行解析。解析的過程以下所示:
知道了瀏覽器加載網頁的步驟,咱們就能夠從上面每個環節採起」相對合適「的策略優化加載速度。 好比上面第二步驟會進行dns查找,那麼dns查找是須要時間的,若是提早將dns解析並進行緩存,就能夠減小這部分性能損失。在好比創建TCP鏈接以後,保持長鏈接的狀況下能夠串行發送請求。熟悉異步的朋友確定知道串行的損耗是很大的,它的加載時間取決於資源加載時間的和。而採起並行的方式是全部加載時間中最長的。這個時候咱們能夠考慮減小http 請求或者使用支持並行方式的協議(好比HTT2協議)。若是你們熟悉瀏覽器的原理或者仔細觀察網絡加載圖的化,會發現同時加載的資源有一個上限(根據瀏覽器不一樣而不一樣),這是瀏覽器對於單個域名最大創建鏈接個數的限制,因此能夠考慮增長多個domain來進行優化。相似的還有不少,留給你們思考。可是總結下來只有兩點,一是加載優化,即提升資源加載的速度。第二個是渲染優化,即資源拿到以後到解析完成的階段的優化。
通過上面簡單的講解,我想你們對瀏覽器加載HTML開始到頁面呈現出來,有了一個大概的認識,後面我會更詳細地講解這個過程。有一個名詞叫關鍵路徑(Critical Path),它指的是從瀏覽器收到 HTML、CSS 和 JavaScript 字節到對其進行必需的處理,從而將它們轉變成渲染的像素。這一過程當中有一些中間步驟,優化性能其實就是了解這些步驟中發生了什麼。記住關鍵路徑上的資源有HTML,CSS,JavaScript,其中並不包括圖片,雖然圖片在咱們的應用中很是廣泛,可是圖片並不會阻止用戶的交互,所以不計算到關鍵路徑,關於圖片的優化我會在下面的小節中重點介紹。
爲了讓你們有更清晰地認識,我將上面瀏覽器加載網站步驟中的第七步中的CSSOM和DOM以及render tree的構建過程,更詳細地講解一下。
瀏覽器請求服務端的HTML文件,服務端響應字節流給瀏覽器。瀏覽器接受到HTML而後根據指定的編碼格式進行解碼。完成以後會分析HTML內容,將HTML分紅一個個token,而後根據不一樣token生成不一樣的DOM,最後根據HTML中的層級結構生成DOM樹。
其中要注意的是,若是碰到CSS標籤和JavaScript標籤(不是async或者defer的js腳本)會暫停渲染,等到資源加載完畢,繼續渲染。若是加載了CSS文件(內斂樣式同理),會在加載完成CSS以後生成CSSOM。CSSOM的生成過程相似,也是將CSS分紅一個個token,而後根據不一樣token生成CSSOM,CSSOM是用來控制DOM的樣式的。最後將DOM和CSSOM合成render tree。
CSS 是阻塞渲染的資源。須要將它儘早、儘快地下載到客戶端,以便縮短首次渲染的時間。
爲弄清每一個對象在網頁上的確切大小和位置,瀏覽器從渲染樹的根節點開始進行遍歷,根據盒模型和CSS計算規則生成計算樣式(chrome中叫computed style),最後調用繪製線程將DOM繪製到頁面上。所以優化上面每個步驟都很是重要。如今咱們有了清晰的認識,關鍵資源HTML是一切的起點,沒有HTML後面就沒有意義。CSS應該儘快下載並解析,一般咱們將css放在head裏面優先加載執行,就像app shell的概念同樣。咱們應該優先給用戶呈現最小子集,而後慢慢顯示其餘的內容,就好像PJPEG(progressive jpeg)同樣。以下圖是一個漸進式渲染的一個例子(圖片來自developers.google.com):
通過上面的分析,咱們知道了關鍵路徑。咱們能夠藉助chrome開發工具查看瀑布圖,分析網站的關鍵路徑,分析加載緩慢,影響網站速度的瓶頸點。
也可使用一些工具檢測,好比前面提到的web performance test,也能夠嘗試下Lighthouse。
在後面的小節,我會介紹performance api,你們能夠在前端埋點,而後分析網站的性能指標,這也是對其餘分析手法的一個重要補充。
用戶從打開頁面開始到有頁面開始呈現爲止。白屏時間長是沒法忍受的,所以有了不少的縮短白屏時間的方法。 好比減小首屏加載內容,首屏內容漸出等。白屏的測量方法最古老的方法是這樣的:
<head>
<script> var t = new Date().getTime(); </script>
<link src="">
<link src="">
<link src="">
<script> tNow = new Date().getTime() - t; </script>
</head>
複製代碼
可是上面這種只能測量首屏有html內容的狀況,好比像react這樣客戶端渲染的方式就不行了。若是採用客戶端渲染的方式,就須要在首屏接口返回, 並渲染頁面的地方打點記錄。
經過相似的方法咱們還能夠查看圖片等其餘資源的加載時間,以圖片爲例:
<img src="xx" id="test" />
<script> var startLoad = new Date().getTime() document.getElementById('test').addEventListener('load', function(){ var duration = new Date().getTime() - startLoad(); }, false) </script>
複製代碼
經過這種方法未免太麻煩,還在瀏覽器performance api 提供了不少有用的接口,方便你們計算各類性能指標。下面performance api 會詳細講解。
咱們所說的首屏時間,就是指用戶在沒有滾動時候看到的內容渲染完成而且能夠交互的時間。至於加載時間,則是整個頁面滾動到底部,全部內容加載完畢並可交互的時間。用戶能夠進行正常的事件輸入交互操做。
firstscreenready - navigationStart
複製代碼
這個時間就是用戶實際感知的網站快慢的時間。firstscreenready 沒有這個 performance api, 並且不一樣的渲染手段(服務端渲染和客戶端渲染計算方式也不一樣),不能一律而論。具體計算方案,這邊文章寫得挺詳細的。首屏時間計算
一般網頁以兩個事件的觸發時間來肯定頁面的加載時間.
DOMContentLoaded 事件,表示直接書寫在HTML頁面中的內容但不包括外部資源被加載完成的時間,其中外部資源指的是css、js、圖片、flash等須要產生額外HTTP請求的內容。
onload 事件,表示連同外部資源被加載完成的時間。
domComplete - domLoading
複製代碼
上面介紹了古老的方法測量關鍵指標,主要原理就是基於瀏覽器從上到下加載的原理。只是上面的方法比較麻煩,不適合實際項目中使用。 實際項目中仍是採用打點的方式。 即在關鍵的地方埋點,而後根據須要將打點信息進行計算獲得咱們但願看到的各項指標,performance api 就是這樣一個東西。
The Performance interface provides access to performance-related information for the current page. It's part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API.
------ 摘自MDN
在瀏覽器console中輸入performance.timing
返回的各字節跟下面的performance流程的各狀態一一對應,並返回時間。這個和js中直接new Date().getTime()的時間是不同的。 這個時間和真實時間沒有關係,並且perfermance api精確度更高。
有了這個performance api 咱們能夠很方便的計算各項性能指標。若是performamce api 」埋的點「不夠咱們用,咱們還能夠自定義一些咱們關心的指標,好比請求時間(成功和失敗分開統計),較長js操做時間,或者比較重要的功能等。總之,只要咱們想要統計的,咱們均可以藉助performance api 輕鬆實現。
performance api 更多介紹請查看 https://developer.mozilla.org/en-US/docs/Web/API/Performance
監控是基於日誌的
一個格式良好,內容全面的日誌是實現監控的重要條件,能夠說基礎決定上層建築。 良好的的日誌系統一般有如下幾個部分構成:
日誌由產生到進入日誌系統的過程。 好比rsyslog生成的日誌,經過logstash(transport and process your logs, events, or other data)接入到日誌系統。這種比較簡單,因爲是基於原生linux的日誌系統,學習使用成本也比較低。公司不一樣系統若是須要介入日誌系統,只須要將日誌寫入log目錄,經過logstash等採集就能夠了。
這部分是日誌系統的核心,處理層能夠將接入層產生的日誌進行分析。過濾日誌發送到監控中心(監控系統狀態)和存儲中心(數據彙總,查詢等)
將日誌入庫,根據業務狀況,創建索引。這部分一般還能夠接入像elastic search這樣的庫,提供日誌的查詢,上面的logstash 就是elastic家族的。
除了上面核心的幾層,一般還有其餘層完成更爲細化的工做。
一般來講,一個公司的日誌有如下幾個方面
記錄一些關鍵指標,具體的關鍵指標能夠參閱「瀏覽器性能指標」一節。
記錄後端的服務器錯誤(500,502等),前端的腳本錯誤(script error)。
記錄硬件資源的使用率,好比內存,網絡帶寬和硬盤等。
記錄業務方比較關心的用戶的操做。方便根據用戶報的異常,定位問題。
統計日誌一般是基於存儲日誌的內容進行統計。統計日誌有點像數據庫視圖的感受,經過視圖屏蔽了數據庫的結構信息,將數據庫一部份內容透出到用戶。用戶行爲分析等一般都是基於統計日誌分析的。有的公司甚至介入了可視化的日誌統計系統(好比Kibana)。隨着人工智能的崛起,人工智能+日誌是一個方向。
除了上面介紹的關鍵指標記錄。咱們一般還比較關心接口的響應速度。這時候咱們能夠經過打點的方式記錄。
咱們已經產出了日誌,有了日誌數據源。那麼如何消費數據呢? 如今廣泛的作法是服務端將收集的日誌進行轉儲,並經過可視化手段(圖標等)展現給管理員。還有一種是用戶本身消費,即自產自銷。用戶產生數據,同時本身消費,提供更加的用戶體驗。 詳情查閱 locus 可是性能日誌明顯不能自產自銷,咱們暫時只考慮第一種。
監控平臺大公司基本都有本身的系統。好比有讚的Hawk,阿里的SunFire。小公司一般都是使用開源的監控系統或者乾脆沒有。 我以前的公司就沒有什麼監控平臺,最多隻是阿里雲提供的監控數據而已。因此我在這一方面作了必定的探索。並開始開發朱雀平臺,可是限於精力有限,該計劃最後沒有最終投入使用,仍是蠻惋惜的。性能監測的本質是基於監測的數據,提供方便的查詢和可視化的統計。並對超過臨界值(一般還有持續時長限制)發出警告。 上一節介紹了性能監控平臺,提到了性能監控平臺的兩個組成部分,一個是生產者一個是消費者。 這節介紹如何搭建一個監控平臺。那麼我先來看下總體的架構
爲了方便講解,這裏只實現一個最簡化的模型,讀者能夠在此基礎上進一步劃分子系統,好比接入SSO,存儲展現分離等。
客戶端一方面上報埋點信息,另外一方面上報軌跡信息。
這一部分主要藉助一些手段,好比performance api 將網頁相關加載時間信息上報到後端。
performance.getEntriesByType("resource").forEach(function(r) {
console.log(r.name + ": " + r.duration)
})
複製代碼
另外一方面對特定的異步請求接口,打點。對用戶全部的交互操做打點(點擊,hover等)
const startTime = new Date().getTime();
fetch(url)
.then(res => {
const endTime = new Date().getTime();
report(url , 'success', endTime - startTime);
})
.catch(err => {
const endTime = new Date().getTime();
report(url, 'failure', endTime - startTime);
})
複製代碼
上傳軌跡信息就簡單了。若是是頁面粒度,直接在頁面上報就能夠了。若是使用了前端路由,還能夠在路由的鉤子函數中進行上報。
pageA.js
// 上報軌跡
report('pageA',{userId: '876521', meta: {}})
複製代碼
這樣咱們就有了數據源了。
服務端已經有了數據,後端須要將數據進行格式化,並輸出。
客戶端將本身的信息上報到server,由server進行統計彙總,並在合適的時候將處理後的數據下發到客戶端,指導客戶端的行爲(如預加載)。
前面說了客戶端上傳的信息大概是
{
userId: 876521,
page: "index",
area: "",
age: "",
// 其餘羣體特徵
}
複製代碼
咱們稱地域,年齡等爲羣體特徵,羣體特徵對於分析統計有很是重要的意義。
咱們能夠對單用戶進行彙總,也能夠對羣體特徵進行彙總從而預測客戶的行爲。
咱們彙總的數據多是:
{
userId: '876521',
pages: {
"index": {
"detail": 0.5,
"return": 0.4,
"personnal-center": 0.1
}
}
}
複製代碼
如上是以用戶爲緯度進行分析。上面的數據表明,若是用戶876521在首頁(index),那麼ta下一步訪問詳情頁(detail)的機率是50%,訪問我的中心(personal-center)的機率爲10%,退出頁面機率爲40%。
咱們就能夠在可能的狀況下,用戶停留在首頁的時候預加載詳情頁。
咱們還能夠對羣體特徵進行彙總,
彙總的結果多是:
{
age: 'teen',
pages: {
"index": {
"detail": 0.5,
"return": 0.4,
"personnal-center": 0.1
}
}
}
複製代碼
其實和上面差很少,不過這裏並非只是用來指導某一個用戶,而是能夠指導同一個羣體特徵(這裏指同一年齡段)的用戶。
客戶端會上傳性能信息給zhuque server。
zhuque server 在這裏主要有兩個職責:
這部分一般作起來簡單,作好難。 我在這方面經驗不夠多,就不誤導你們了。
警報種類有不少,好比郵件,電話,短信,釘釘等。 咱們只要設置好觸發條件,而後寫一個定時任務或者在請求級別進行檢查,若是知足就觸發警報便可。邏輯很是簡單。
定時任務對系統的壓力較小,可是及時性較低,適合對實時性要求不強的業務。 請求級別檢查謉系統壓力較大,可是及時性有保障,適合對實時性要求很是高的業務。
要作性能優化,首先要對系統運行的過程有一個完整的理解,而後從各個環節分析,找到系統瓶頸,從而進行優化。在這裏我不羅列性能優化的各類手段,而是從前端三層角度逐個描述下性能優化的常見優化方向和手段。若是你們但願有一個完整的優化清單, 這裏有一份比較完整的Front-End-Checklist,對於性能優化,有必定的借鑑意義。另外你也能夠訪問webpagetest測試你的網站的性能,並針對網站提供的反饋一步步優化你的網站加載速度,這些內容不在本文論述範圍。 性能優化一個最重要的原則是:永遠呈現必要的內容,咱們能夠經過懶加載非首屏資源,或者採用分頁的方式將數據」按需加載「。下面講述一些具體的優化手段。 不少人都知道,前端將應用分爲三層,分別是結構層,表現層和行爲層。咱們就從三層角度講一下性能優化的方向。
結構層指的是DOM結構,而DOM結構一般是由HTML結構決定的,所以咱們主要分析下HTML結構的性能優化點。 咱們知道DOM操做是很是昂貴的,這在前面講述前端發展歷史的時候也提到了。如何減小DOM數量,減小DOM操做是優化須要 重點關注的地方。
說到HTML優化,不得不提AMP HTML。 AMP的核心思想是提供移動端更佳的用戶體驗,。由AMP HTML, AMP JS 和 AMP Cache 三個核心部分組成。
AMP HTML is HTML with some restrictions for reliable performance.
下面是典型的AMP HTML
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<link rel="canonical" href="hello-world.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>Hello World!</body>
</html>
複製代碼
能夠看出AMP HTML由普通的HTML標籤和amp標籤組成。amp標籤是作什麼呢?且聽我跟你說,DOM雖然操做比較昂貴,可是不一樣的DOM效率也是不意義的。好比渲染一個a標籤和渲染一個img或者table時間確定不是同樣的。咱們稱a標籤這樣渲染較快的元素爲輕元素。稱table,img這樣的元素爲重元素。那麼咱們就應該儘可能避免重元素的出現,好比table 能夠採用ul li 實現。 img之因此比較慢的緣由是圖片下載雖然是異步的,可是會佔用網絡線程,同時會多發一個請求(瀏覽器併發請求數是有限制的),所以能夠進一步封裝稱輕元素(好比x-image,組件內部能夠延遲發送圖片請求,等待主結構渲染完畢再發圖片請求)。 能夠考慮將其封裝爲web-component或者其餘組件形式(如react組件)
回到剛纔AMP HTML, 其實在amp 中 有一個amp-image這樣的接口,大概能夠根據須要本身實現,上面咱們說的x-image 其實就是實現了amp接口規範的組件。
前面說到了儘量使用輕元素。那麼除了使用輕元素,還有一點也很重要,就是減小DOM數量。減小DOM數量的一個重要的途徑就是減小冗餘標籤。好比咱們經過新增長一個元素清除浮動。
<div class="clear"></div>
複製代碼
不過目前都是採用僞元素實現了。另外一個途徑是減小嵌套層次。不少人都會在<form>
或者<ul>
外邊包上<div>
標籤,爲何加上一個你根本不須要的<div>
標籤呢?實際上你徹底能夠用CSS selector,實現一樣的效果。 重要的是,這種代碼我見過不少。
<div class="form">
<form>
...
</form>
</div>
複製代碼
徹底能夠這樣寫:
<form class="form">
...
</form>
複製代碼
表現層就是咱們一般使用的CSS。CSS通過瀏覽器的解析會生成CSS TREE,進而和DOM TREE合成 RENDER TREE。有時候一些功能徹底能夠經過CSS去實現,沒有必要使用javaScript,尤爲是動畫方面,CSS3增長了transition 和 transform 用來實現動畫,開發者甚至能夠經過3D加速功能來充分發揮GPU的性能。所以熟練使用CSS,並掌握CSS的優化技巧是必不可少的。CSS 的性能優化一般集中在兩方面:
提升加載性能就是減小加載所消耗的時間。簡單說就是減少CSS文件的大小,提升頁面的加載速度,盡能夠的利用http緩存等。代碼層面咱們要避免引入不須要的樣式,合理運用繼承減小代碼。
<body>
<div class="list" />
</body>
複製代碼
body { color: #999 }
.list {color: #999} // color 其實能夠繼承body,所以這一行不必
複製代碼
其餘能夠繼承的屬性有color,font-size,font-family等。
一般來講這部分和JS等靜態資源的優化道理是同樣的。只不過目前網站有一個理念是框架優先,即先加載網站的主題框架,這部分一般是靜態部分,而後動態加載數據,這樣給用戶的感受是網站」很快「。而這部分的靜態內容,一般能夠簡單的HTML結構(Nav + footer),加上CSS樣式來完成。 這就要求主題框架的CSS優先加載,咱們設置能夠將這部分框架樣式寫到內斂樣式中去,可是有的人以爲這樣不利於代碼的維護。
瀏覽器對不一樣的代碼執行效率是不一樣的,複雜的樣式(多層嵌套)也會下降css解析效率,所以能夠將複雜的嵌套樣式進行轉化。
.wrapper .list .item .success {}
// 能夠寫成以下:
.wrapper .list .item-success {}
複製代碼
還有一部分是網站的動畫,動畫一般來講要作到16ms之內,以讓用戶感受到很是流暢。另外咱們還能夠經過3D加速來充分應用GPU的性能。 這裏引用於江水的一句話:
只有在很是複雜的頁面,樣式很是多的時候,CSS 的性能瓶頸纔會凸顯出來,這時候更多要考慮的應該是有沒有必要作這麼複雜的頁面。
行爲層指的是用戶交互方面的內容,在前端主要經過JavaScript實現。目前JavaScript 規範已經到es2017。 前端印象較爲深入的是 ES6(也就是ES2015),由於ES5是2009年發佈的,以後過了6年,也就是2015年ES6才正式發佈,其中增長了許多激動人心的新特性, 被廣大前端所熟悉。甚至曾一度稱目前前端狀態是536(HTML5,CSS3,ES6),可見其影響力。
絕不誇張的說,目前前端項目絕大多數代碼都是javascript。既然js用的這麼多,爲何不多有人談js性能優化呢? 一是由於如今工業技術的發展,硬件設備的性能提高,致使前端計算性能一般不認爲是一個系統的性能瓶頸。二是隨着V8引擎的發佈,js執行速度獲得了很大的提高。三是由於計算性能是本地CPU和內存的工做,其相對於網路IO根本不是一個數量級,所以人們更多關注的是IO方面的優化。那麼爲何還要將js性能優化呢?一方面是目前前端會經過node作一些中間層,甚至是後端,所以須要重點關注內存使用狀況,這和瀏覽器是截然不同的。另外一方面是由於前端有時候也會寫一個複雜計算,也會有性能問題。 最後一點是咱們是否能夠經過JS去優化網絡IO的性能呢,好比使用JS API 操做 webWorker 或者使用localStorage緩存。
前端偶爾也會有一些數據比較大的計算。 對於一些複雜運算,一般咱們會將計算結果進行緩存,以供下次使用。前面提到了純函數的概念,要想使用計算緩存,就要求函數要是純函數。一個簡單的緩存函數代碼以下:
// 定義
function memoize(func) {
var cache = {};
var slice = Array.prototype.slice;
return function() {
var args = slice.call(arguments);
if (args in cache)
return cache[args];
else
return (cache[args] = func.apply(this, args));
}
}
// 使用
function cal() {}
const memoizeCal = memoize(cal);
memoizeCal(1) // 計算,並將結果緩存
memoizeCal(1) // 直接返回
複製代碼
前面講了計算方面的優化,它的優化範圍是比較小的。由於並非全部系統都會有複雜計算。可是網絡IO是全部系統都存在的,並且網絡IO是不穩定的。網絡IO的 速度和本地計算根本不是一個數量級,好在咱們的瀏覽器處理網絡請求是異步的(固然能夠代碼控制成同步的)。一種方式就是經過本地緩存,將網絡請求結果存放到本地,在下次請求的時候直接讀取,不須要重複發送請求。一個簡單的實現方法是:
function cachedFetch(url, options) {
const cache = {};
if (cache[url]) return cache[url];
else {
return fetch(url, options).then(res) {
cache[url] = res
return res;
}
}
}
複製代碼
固然上面的粗暴實現有不少問題,好比沒有緩存失效策略(好比能夠採用LRU策略或者經過TTL),可是基本思想是這樣的。 這種方式的優勢很明顯,就是顯著減小了系統反饋時間,固然缺點也一樣明顯。因爲使用了緩存,當數據更新的時候,就要考慮緩存更新同步的問題,不然會形成數據不一致,形成很差的用戶體驗。
數據結構和算法的優化是前端接觸比較少的。可是若是碰到計算量比較大的運算,除了運用緩存以外,還要藉助必定的數據結構優化和算法優化。 好比如今有50,000條訂單數據。
const orders = [{name: 'john', price: 20}, {name: 'john', price: 10}, ....]
複製代碼
我須要頻繁地查找其中某我的某天的訂單信息。 咱們能夠採起以下的數據結構:
const mapper = {
'john|2015-09-12': []
}
複製代碼
這樣咱們查找某我的某天的訂單信息速度就會變成O(1),也就是常數時間。你能夠理解爲索引,由於索引是一種數據結構,那麼咱們也可使用其餘數據結構和算法適用咱們各自獨特的項目。對於算法優化,首先就要求咱們可以識別複雜度,常見的複雜度有O(n) O(logn) O(nlogn) O(n2)。而對於前端,最基本的要識別糟糕的複雜度的代碼,好比n三次方或者n階乘的代碼。雖然咱們不須要寫出性能很是好的代碼,可是也儘可能不要寫一些複雜度很高的代碼。
經過HTML5的新API webworker,使得開發者能夠將計算轉交給worker進程,而後經過進程通訊將計算結果回傳給主進程。毫無疑問,這種方法對於須要大量計算有着很是明顯的優點。
代碼摘自Google performance:
var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);
// The main thread is now free to continue working on other things...
dataSortWorker.addEventListener('message', function(evt) {
var sortedData = evt.data;
// Update data on screen...
});
複製代碼
因爲WebWorker 被作了不少限制,使得它不能訪問諸如window,document這樣的對象,所以若是你須要使用的話,就不得不尋找別的方法。
一種使用web worker的思路就是分而治之,將大任務切分爲若干個小任務,而後將計算結果彙總,咱們一般會藉助數組這種數據結構來完成,下面是一個例子:
// 不少小任務組成的數組
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
// 使用更新的api requestAnimationFrame而不是setTimeout能夠提升性能
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var taskFinishTime;
do {
// Assume the next task is pushed onto a stack.
var nextTask = taskList.pop();
// Process nextTask.
processTask(nextTask);
// Go again if there’s enough time to do the next task.
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);
if (taskList.length > 0)
requestAnimationFrame(processTaskList);
}
複製代碼
線程安全問題都是由全局變量及靜態變量引發的。 若每一個線程中對全局變量、靜態變量只有讀操做,而無寫操做,通常來講,這個全局變量是線程安全的;如有多個線程同時執行寫操做,就須要考慮線程同步,就可能產生線程安全問題。
你們能夠沒必要太擔憂,web worker已經在這方面作了不少努力,例如你沒有辦法去訪問非線程安全的組件或者是 DOM,此外你還須要經過序列化對象來與線程交互特定的數據。所以你們若是想寫出線程不安全的代碼,還真不是那麼容易的。
V8是由Google提出的,它經過將js代碼編譯成機器碼,而非元組碼或者解釋它們,進而提升性能。V8內部還封裝了不少高效的算法,不少開發者都會研究V8的源碼來提高本身。 這裏有些js優化的實踐,詳情可看下這篇文章 還有不少其餘有趣的研究。
Benedikt Meurer(Tech Lead of JavaScript Execution Optimization in Chrome/V8)本人致力於V8的性能研究,寫了不少有深度的文章,而且開源了不少有趣的項目,有興趣的能夠關注一下。
前端中的內存泄漏不是很常見,可是仍是有必要知道一下,最起碼可以在出現問題的時候去解決問題。更爲低級的語言如C語言,有申請內存malloc和銷燬內存free的操做。而在高級語言好比java和js,屏蔽了內存分配和銷燬的細節,而後經過GC(垃圾回收器)去清除不須要使用的內存。
只有開發人員本身知道何時應該銷燬內存。
好在內存銷燬仍是有必定規律可循,目前GC的垃圾回收策略主要有兩種,一種是引用計數,另外一種是不可達檢測。目前主流瀏覽器都實現了上述兩種算法,而且都會綜合使用兩種算法對內存進行優化。可是確實還存在上述算法沒法覆蓋的點,好比閉包。所以仍是依賴於開發者自己的意識,所以瞭解下內存泄漏的原理和解決方案仍是很是有用的。下面講述容易形成內存泄漏的幾種狀況。
函數調用是有必定的開銷的,具體爲須要爲函數分配棧空間。若是遞歸調用的話,有可能形成爆棧。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(100) // 一切正常
factorial(1000) // 有可能爆棧,可是如今瀏覽器作了優化,一般會輸出Infinite
}
複製代碼
若是在這裏你使用了比較複雜的運算狀況就會變糟,若是再加上閉包就更糟糕了。
因爲js沒有私有屬性,js若是要實現私有屬性的功能,就要藉助閉包實現。
function closure() {
var privateKey = 1;
return function() {
return privateKey
}
}
複製代碼
可是因爲js的垃圾回收機制,js會按期將沒有引用的內存釋放,若是使用閉包,函數會保持變量的引用,致使垃圾回收週期內不能將其銷燬,濫用閉包則可能產生內存泄漏。
上面從前端三層角度分析了性能優化的手段,可是還有一個,並且是佔比很是大的資源沒有提到,那就是圖片。俗話說,一圖勝千言,圖片在目前的網站中佔據了網站中大部分的流量。雖然圖片不會阻止用戶的交互,不影響關鍵路徑,可是圖片加載的速度對於用戶體驗來講很是重要。尤爲是移動互聯網如此發達的今天,爲用戶節省流量也是很是重要的。所以圖片優化主要有兩點,一點是在必要的時候使用圖片,沒必要要的時候換用其餘方式。另外一種就是壓縮圖片的體積。
首先要問問本身,要實現所需的效果,是否確實須要圖像。好的設計應該簡單,並且始終能夠提供最佳性能。若是您能夠消除圖像資源(與 HTML、CSS、JavaScript 以及網頁上的其餘資源相比,須要的字節數一般更大),這種優化策略就始終是最佳策略。不過,若是使用得當,圖像傳達的信息也可能賽過千言萬語,所以須要由您來找到平衡點。
首先來看下圖片體積的決定因素。這裏可能須要一些圖像學的相關知識。圖片分爲位圖和矢量圖。位圖是用比特位來表示像素,而後由像素組成圖片。位圖有一個概念是位深,是指存儲每一個像素所用的位數。那麼對於位圖計算大小有一個公式就是圖片像素數 * 位深 bits。 注意單位是bits,也能夠換算成方便查看的kb或者mb。
圖片像素數 = 圖片水平像素數 * 圖片垂直像素數
而矢量圖由數學向量組成,文件容量較小,在進行放大、縮小或旋轉等操做時圖象不會失真,缺點是不易製做色彩變化太多的圖象。那麼矢量圖是電腦通過數據計算獲得的,所以佔據空間小。一般矢量圖和位圖也會相互轉化,好比矢量圖要打印就會點陣化成位圖。
下面講的圖片優化指的是位圖。知道了圖片大小的決定因素,那麼減小圖片大小的方式就是減小分辨率或者採用位深較低的圖片格式。
咱們平時開發的時候,設計師會給咱們1x2x3x的圖片,這些圖片的像素數是不一樣的。2x的像素數是1x的 2x2=4倍,而3x的像素數高達3x3=9倍。圖片直接大了9倍。所以前端使用圖片的時候最好不要直接使用3倍圖,而後在不一樣設備上平鋪,這種作法會須要依賴瀏覽器對其進行從新縮放(這還會佔用額外的 CPU 資源)並以較低分辨率顯示,從而下降性能。 下面的表格數據來自Google Developers
請注意,在上述全部狀況下,顯示尺寸只比各屏幕分辨率所需資源「小 10 個 CSS 像素」。不過,多餘像素數及其相關開銷會隨圖像顯示尺寸的增長而迅速上升!所以,儘管您可能沒法保證以精確的顯示尺寸提供每個資源,但您應該確保多餘像素數最少,並確保特別是較大資源以儘量接近其顯示尺寸的尺寸提供。
咱們可使用媒體查詢或者srcset等針對不一樣屏幕加載不一樣資源。可是9倍這樣的大小咱們仍是很難接受。所以有了下面的方法。
位深是用來表示一個顏色的字節數。位深是24位,表達的是使用256(2的24/3次方)位表示一個顏色。所以位深越深,圖片越精細。若是可能的話,減小位深能夠減小體積。
前面說了圖片大小 = 圖片像素數 * 位深
, 其實更嚴格的是圖片大小 = 圖片像素數 * 位深 * 圖片質量
, 所以圖片質量(q)越低,圖片會越小。 影響圖片壓縮質量的因素有不少,好比圖片的顏色種類數量,相鄰像素顏色相同的個數等等。對應着有不少的圖片壓縮算法,目前比較流行的圖片壓縮是webp格式。所以條件容許的話,儘可能使用webp格式。
有了上面的理論以後,咱們須要將理論具體運用在實踐上。 咱們平時開發的時候會有縮略圖的需求,若是你將原始圖片加載過來經過css控制顯示的話,你會發現你會加載一個很是大的圖片,然而自己應該很小纔對。那麼若是咱們能夠控制只下載咱們須要縮略顯示的部分就行了。咱們但願能夠經過https://test.imgix.net/some_file?w=395&h=96&crop=faces
的方式指定圖片的大小,從而減小傳輸字節的浪費。已經有圖片服務商提供了這樣的功能。好比imgix。
imgix有一個優點就是可以找到圖片中有趣的區域並作裁剪。而不是僅僅裁剪出圖片的中心
上面提到的webp最好也能夠經過CDN廠商支持,即咱們上傳圖片的時候,CDN廠商對應存儲一份webp的。好比咱們上傳一個png圖片https://img.alicdn.com/test/TB1XFdma5qAXuNjy1XdXXaYcVXa-29-32.png
。而後咱們能夠經過https://img.alicdn.com/test/TB1XFdma5qAXuNjy1XdXXaYcVXa-29-32.webp
訪問其對應的webp資源。咱們就能夠根據瀏覽器的支持狀況加載webp或者png圖片了。
第二個有效的方式是懶加載,一個重要的思想就是隻加載應該在此時展現的圖片。假如你正在使用react,那麼你能夠經過react-lazyload使用圖片懶加載。其餘框架能夠自行搜索。
import LazyLoad from 'react-lazyload';
<LazyLoad once height={200} offset={50}> <img srcSet={xxx} sizes={xxxxx} /> </LazyLoad> 複製代碼
若是你已經採起了很是多的優化手段,用戶仍是以爲很是慢,怎麼辦呢?要知道,性能好很差不是數據測量出來的,而是用戶的直觀感受,就像我開篇講述的那樣。有一個方法能夠在速度不變的狀況下,讓用戶感受更快,那就是合理使用動畫。如一個寫着當前90%進度的進度條,一個奔跑的小熊?可是必定要慎用,由於不合理的動畫設計,反而讓用戶反感,試想一下,當你看到一個期待已久的肯定按鈕,可是它被一個奔跑的小熊擋在了身後,根本點不到,你心裏會是怎樣的?
另外我在這裏只是提供了性能優化的思路,並無覆蓋性能優化的全部點,好比google的protobuffer能夠減小先後端傳輸數據的體積,進而提高性能。可是咱們 有了上面的優化理論和思想,我相信這些東西都是能夠看到並作到的
PS:容許我差一波廣告,兌吧前端正在招人~有興趣的戳我投遞簡歷
歡迎關注個人公衆號,不定時更新。