首先,咱們先來看看 👀 雅虎軍規 的 35 條。javascript
- 儘可能減小 HTTP 請求個數——須權衡
- 使用 CDN(內容分發網絡)
- 爲文件頭指定 Expires 或 Cache-Control ,使內容具備緩存性。
- 避免空的 src 和 href
- 使用 gzip 壓縮內容
- 把 CSS 放到頂部
- 把 JS 放到底部
- 避免使用 CSS 表達式
- 將 CSS 和 JS 放到外部文件中
- 減小 DNS 查找次數
- 精簡 CSS 和 JS
- 避免跳轉
- 剔除重複的 JS 和 CSS
- 配置 ETags
- 使 AJAX 可緩存
- 儘早刷新輸出緩衝
- 使用 GET 來完成 AJAX 請求
- 延遲加載
- 預加載
- 減小 DOM 元素個數
- 根據域名劃分頁面內容
- 儘可能減小 iframe 的個數
- 避免 404
- 減小 Cookie 的大小
- 使用無 cookie 的域
- 減小 DOM 訪問
- 開發智能事件處理程序
- 用 代替 @import
- 避免使用濾鏡
- 優化圖像
- 優化 CSS Spirite
- 不要在 HTML 中縮放圖像——須權衡
- favicon.ico要小並且可緩存
- 保持單個內容小於25K
- 打包組件成複合文本
如對 雅虎軍規 的具體細則內容不是很瞭解,可自行去各搜索 🔍 引擎 ,搜索 雅虎軍規 瞭解詳情。css
對於 前端性能優化 天然要關注 首屏 打開速度,而這個速度,很大因素是花費在網絡請求上,那麼怎麼減小網絡請求的時間呢?html
CDN
加速因此 壓縮、合併 就是一個解決方案,固然能夠用 gulp
、 webpack
、 grunt
等構建工具 壓縮、合併前端
JS、CSS
壓縮 合併例如:gulp js、css
壓縮、合併代碼以下 👇java
//壓縮、合併js gulp.task('scripts', function () { return gulp.src([ './public/lib/fastclick/lib/fastclick.min.js', './public/lib/jquery_lazyload/jquery.lazyload.js', './public/lib/velocity/velocity.min.js', './public/lib/velocity/velocity.ui.min.js', './public/lib/fancybox/source/jquery.fancybox.pack.js', './public/js/src/utils.js', './public/js/src/motion.js', './public/js/src/scrollspy.js', './public/js/src/post-details.js', './public/js/src/bootstrap.js', './public/js/src/push.js', './public/live2dw/js/perTips.js', './public/live2dw/lib/L2Dwidget.min.js', './public/js/src/love.js', './public/js/src/busuanzi.pure.mini.js', './public/js/src/activate-power-mode.js' ]).pipe(concat('all.js')).pipe(minify()).pipe(gulp.dest('./public/dist/')); }); // 壓縮、合併 CSS gulp.task('css', function () { return gulp.src([ './public/lib/font-awesome/css/font-awesome.min.css', './public/lib/fancybox/source/jquery.fancybox.css', './public/css/main.css', './public/css/lib.css', './public/live2dw/css/perTips.css' ]).pipe(concat('all.css')).pipe(minify()).pipe(gulp.dest('./public/dist/')); });
而後,再把 壓縮、合併 的 JS、CSS
放入 CDN
, 👀 看看效果如何jquery
如圖:* 壓縮、合併 且放入 CND
以後的效果 *webpack
以上是 lishaoy.net 清除緩存後的 首頁 請求速度。git
可見,請求時間是 4.59 s ,總請求個數 51 , 而 js
的請求個數是 8 ,css
的請求個數是 3 (其實就 all.css 一個,其它 2 個是 Google瀏覽器加載的), 而沒使用 壓縮、合併 時候,請求時間是 10 多秒,總請求個數有 70 多個,js
的請求個數是 20 多個 ,對比請求時間 性能 提高 1倍 多github
如圖:有緩存下的首頁效果web
基本都是秒開 😝
Tips:在 壓縮、合併 後,單個文件控制在 25 ~ 30 KB左右,同一個域下,最好不要多於5個資源
例如:gulp
圖片壓縮代碼以下 👇
//壓縮image gulp.task('imagemin', function () { gulp.src('./public/**/*.{png,jpg,gif,ico,jpeg}') .pipe(imagemin()) .pipe(gulp.dest('./public')); });
圖片的合併能夠採用 CSS Spirite
,方法就是把一些小圖用 PS
合成一張圖,用 css
定位顯示每張圖片的位置
.top_right .phone { background: url(../images/top_right.png) no-repeat 7px -17px; padding: 0 38px; } .top_right .help { background: url(../images/top_right.png) no-repeat 0 -47px; padding: 0 38px; }
而後,把 壓縮 的圖片放入 CDN
, 👀 看看,效果如何
可見,請求時間是 1.70 s ,總請求個數 50 , 而 img
的請求個數是 15 (這裏由於首頁都是大圖,就沒有合併,只是壓縮了) ,可是,效果很好 😀 ,從 4.59 s 縮短到 1.70 s, 性能又提高一倍。
再看看有緩存狀況如何 😏
請求時間是 1.05 s ,有緩存和無緩存基本差很少
Tips:大的圖片在不一樣終端,應該使用不一樣分辨率,而不該該使用縮放(百分比)
整個 壓縮、合併 (js、css、img) 再放入 CDN
,請求時間從 10 多秒 ,到最後的 1.70 s ,性能提高 5 倍多,可見,這個操做必要性。
緩存會根據請求保存輸出內容的副本,例如 頁面、圖片、文件,當下一個請求來到的時候:若是是相同的URL
,緩存直接使 用本地的副本響應訪問請求,而不是向源服務器再次發送請求。所以,能夠從如下 2 個方面提高性能。
咱們用兩幅圖來了解下瀏覽器的 緩存機制
瀏覽器第一次請求
第一次請求
瀏覽器再次請求
再次請求
從以上兩幅圖中,能夠清楚的瞭解瀏覽器 緩存 的過程。 首次訪問一個 URL
,沒有 緩存 ,可是,服務器會響應一些 header
信息,如:expires、cache-control、last-modified、etag
等,來記錄下次請求是否緩存、如何緩存。 再次訪問這個 URL
時候,瀏覽器會根據首次訪問返回的 header
信息,來決策是否緩存、如何緩存。 咱們重點來分析下第二幅圖,實際上是分兩條線路,以下 👇
URL
時,會先獲取資源的 header
信息,判斷是否命中強緩存 (cache-control和expires) ,如命中,直接從緩存獲取資源,包括響應的 header
信息 (請求不會和服務器通訊) ,也就是 強緩存 ,如圖header
信息 (Last-Modified/If-Modified-Since和Etag/If-None-Match) ,由服務器根據請求中的相關 header
信息來比對結果是否協商緩存命中;若命中,則服務器返回新的響應 header
信息更新緩存中的對應 header
信息,可是並不返回資源內容,它會告知瀏覽器能夠直接從緩存獲取;不然返回最新的資源內容,也就是 協商緩存。如今,咱們瞭解到瀏覽器緩存機制分爲 強緩存、協商緩存,再來看看他們的區別 👇
緩存策略 | 獲取資源形式 | 狀態碼 | 發送請求到服務器 |
---|---|---|---|
強緩存 | 從緩存取 | 200(from memory cache) | 否,直接從緩存取 |
協商緩存 | 從緩存取 | 304(not modified) | 是,經過服務器來告知緩存是否可用 |
與強緩存相關的 header
字段有兩個:
expires: 這是 http1.0
時的規範,它的值爲一個絕對時間的 GMT 格式的時間字符串,如 Mon, 10 Jun 2015 21:31:12 GMT
,若是發送請求的時間在 expires 以前,那麼本地緩存始終有效,不然就會發送請求到服務器來獲取資源
cache-control: max-age=number
,這是 http1.1
時出現的 header
信息,主要是利用該字段的 max-age
值來進行判斷,它是一個相對值;資源第一次的請求時間和 Cache-Control 設定的有效期,計算出一個資源過時時間,再拿這個過時時間跟當前的請求時間比較,若是請求時間在過時時間以前,就能命中緩存,不然未命中, cache-control 除了該字段外,還有下面幾個比較經常使用的設置值:
ETag
,那麼請求的時候會與服務端驗證,若是資源未被更改,則能夠避免從新下載。CDN
等中間代理服務器。CDN
等中繼緩存服務器對其緩存。Tips:若是 cache-control 與 expires 同時存在的話,cache-control 的優先級高於 expires
協商緩存都是由瀏覽器和服務器協商,來肯定是否緩存,協商主要經過下面兩組 header
字段,這兩組字段都是成對出現的,即第一次請求的響應頭帶上某個字段 ( Last-Modified 或者 Etag ) ,則後續請求會帶上對應的請求字段 (If-Modified-Since 或者 If-None-Match ) ,若響應頭沒有 Last-Modified 或者 Etag 字段,則請求頭也不會有對應的字段。
兩者的值都是 GMT
格式的時間字符串,具體過程:
瀏覽器第一次跟服務器請求一個資源,服務器在返回這個資源的同時,在 respone
的 header
加上 Last-Modified 字段,這個 header
字段表示這個資源在服務器上的最後修改時間
瀏覽器再次跟服務器請求這個資源時,在 request
的 header
上加上 If-Modified-Since 字段,這個 header
字段的值就是上一次請求時返回的 Last-Modified 的值
服務器再次收到資源請求時,根據瀏覽器傳過來 If-Modified-Since 和資源在服務器上的最後修改時間判斷資源是否有變化,若是沒有變化則返回 304 Not Modified
,可是不會返回資源內容;若是有變化,就正常返回資源內容。當服務器返回 304 Not Modified
的響應時,response header
中不會再添加 Last-Modified的header ,由於既然資源沒有變化,那麼 Last-Modified 也就不會改變,這是服務器返回 304
時的 response header
瀏覽器收到 304
的響應後,就會從緩存中加載資源
若是協商緩存沒有命中,瀏覽器直接從服務器加載資源時,Last-Modified 的 Header
在從新加載的時候會被更新,下次請求時,If-Modified-Since 會啓用上次返回的Last-Modified 值
這兩個值是由服務器生成的每一個資源的惟一標識字符串,只要資源有變化就這個值就會改變;其判斷過程與 Last-Modified、If-Modified-Since 相似,與 Last-Modified 不同的是,當服務器返回 304 Not Modified
的響應時,因爲 ETag 從新生成過,response header
中還會把這個 ETag 返回,即便這個 ETag 跟以前的沒有變化。
Tips:Last-Modified與ETag是能夠一塊兒使用的,服務器會優先驗證ETag,一致的狀況下,纔會繼續比對Last-Modified,最後才決定是否返回304。
Service Worker 本質上充當Web應用程序與瀏覽器之間的代理服務器,也能夠在網絡可用時做爲瀏覽器和網絡間的代理。它們旨在(除其餘以外)使得可以建立有效的離線體驗,攔截網絡請求並基於網絡是否可用以及更新的資源是否駐留在服務器上來採起適當的動做。他們還容許訪問推送通知和後臺同步API。
Service worker 能夠解決目前離線應用的問題,同時也能夠作更多的事。 Service Worker 可使你的應用先訪問本地緩存資源,因此在離線狀態時,在沒有經過網絡接收到更多的數據前,仍能夠提供基本的功能(通常稱之爲 Offline First)。這是原生APP 原本就支持的功能,這也是相比於 web app
,原生 app
更受青睞的主要緣由。
再來看看 👀 service worker 能作些什麼:
本文主要以(lishaoy.net)資源緩存爲例,闡述下 service worker如何工做
service worker 初次安裝的生命週期,如圖 🌠
sw生命週期
從上 👆 圖可知,service worker 工做的流程:
service worker URL
經過 serviceWorkerContainer.register()
來獲取和註冊。service worker
安裝完成後,會接收到一個激活事件(activate event)。 onactivate
主要用途是清理先前版本的 service worker
腳本中使用的資源。fetch
和消息 message
事件。service worker
長期不使用或者機器內存有限,則可能會銷燬這個 worker
。Tips:激活成功以後,在 Chrome 瀏覽器裏,能夠訪問 chrome://inspect/#service-workers和 chrome://serviceworker-internals/ 能夠查看到當前運行的service worker ,如圖 👇。
如今,咱們來寫個簡單的例子 🌰
要安裝 service worker
,你須要在你的頁面上註冊它。這個步驟告訴瀏覽器你的 service worker
腳本在哪裏。
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }); }
上面的代碼檢查 service worker API
是否可用,若是可用,service worker /sw.js
被註冊。若是這個 service worker
已經被註冊過,瀏覽器會自動忽略上面的代碼。
在你的 service worker
註冊以後,瀏覽器會嘗試爲你的頁面或站點安裝並激活它。 install
事件會在安裝完成以後觸發。install
事件通常是被用來填充你的瀏覽器的離線緩存能力。你須要爲 install
事件定義一個 callback
,並決定哪些文件你想要緩存.
// The files we want to cache var CACHE_NAME = 'my-site-cache-v1'; var urlsToCache = [ '/', '/css/main.css', '/js/main.js' ]; self.addEventListener('install', function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); });
在咱們的 install callback
中,咱們須要執行如下步驟:
上面的代碼中,咱們經過 caches.open
打開咱們指定的 cache
文件名,而後咱們調用 cache.addAll
並傳入咱們的文件數組。這是經過一連串 promise
(caches.open 和 cache.addAll) 完成的。event.waitUntil
拿到一個 promise
並使用它來得到安裝耗費的時間以及是否安裝成功。
如今咱們已經將你的站點資源緩存了,你須要告訴 service worker
讓它用這些緩存內容來作點什麼。有了 fetch
事件,這是很容易作到的。
每次任何被 service worker
控制的資源被請求到時,都會觸發 fetch
事件,咱們能夠給 service worker
添加一個 fetch
的事件監聽器,接着調用 event
上的 respondWith()
方法來劫持咱們的 HTTP 響應,而後你用能夠用本身的方法來更新他們。
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request); ); });
caches.match(event.request)
容許咱們對網絡請求的資源和 cache
裏可獲取的資源進行匹配,查看是否緩存中有相應的資源。這個匹配經過 url
和 vary header
進行,就像正常的 HTTP 請求同樣。
那麼,咱們如何返回 request
呢,下面 👇 就是一個例子 🌰
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) ); });
上面的代碼裏咱們定義了 fetch
事件,在 event.respondWith
裏,咱們傳入了一個由 caches.match
產生的 promise.caches.match
查找 request
中被 service worker
緩存命中的 response
。 若是咱們有一個命中的 response
,咱們返回被緩存的值,不然咱們返回一個實時從網絡請求 fetch
的結果。
固然,我也可使用第三方庫,例如:lishaoy.net 使用了 sw-toolbox。
sw-toolbox 使用很是簡單,下面 👇 就是 lishaoy.net 的一個例子 🌰
"serviceWorker" in navigator ? navigator.serviceWorker.register('/sw.js').then(function () { navigator.serviceWorker.controller ? console.log("Assets cached by the controlling service worker.") : console.log("Please reload this page to allow the service worker to handle network operations.") }).catch(function (e) { console.log("ERROR: " + e) }) : console.log("Service workers are not supported in the current browser.")
以上是 註冊 一個 service woker
"use strict"; (function () { var cacheVersion = "20180527"; var staticImageCacheName = "image" + cacheVersion; var staticAssetsCacheName = "assets" + cacheVersion; var contentCacheName = "content" + cacheVersion; var vendorCacheName = "vendor" + cacheVersion; var maxEntries = 100; self.importScripts("/lib/sw-toolbox/sw-toolbox.js"); self.toolbox.options.debug = false; self.toolbox.options.networkTimeoutSeconds = 3; self.toolbox.router.get("/images/(.*)", self.toolbox.cacheFirst, { cache: { name: staticImageCacheName, maxEntries: maxEntries } }); self.toolbox.router.get('/js/(.*)', self.toolbox.cacheFirst, { cache: { name: staticAssetsCacheName, maxEntries: maxEntries } }); self.toolbox.router.get('/css/(.*)', self.toolbox.cacheFirst, { cache: { name: staticAssetsCacheName, maxEntries: maxEntries } ...... self.addEventListener("install", function (event) { return event.waitUntil(self.skipWaiting()) }); self.addEventListener("activate", function (event) { return event.waitUntil(self.clients.claim()) }) })();
就這樣搞定了 🍉 (具體的用法能夠去 sw-toolbox 查看)
有的同窗就問,service worker
這麼好用,這個緩存空間究竟是多大?其實,在 Chrome 能夠看到,如圖
能夠看到,大概有 30G ,個人站點只用了 183MB ,徹底夠用了 🍓
最後,來兩張圖
因爲,文章篇幅過長,後續還會繼續總結 架構 方面的優化,例如
以及,渲染 方面的優化,例如
以及,性能測試工具,例如