原文地址: An Extensive Guide To Progressive Web Applicationsjavascript
在本文中,咱們將瞭解瀏覽舊的非PWA網站的用戶的痛點以及PWA使網絡變得更好的但願。您將學習製做很是酷的PWA的大多數重要技術,例如service worker,Web push notification和IndexedDB。css
這是我父親的生日,我想給他訂一塊巧克力蛋糕和一件襯衫。我前往谷歌搜索巧克力蛋糕並點擊搜索結果中的第一個連接。有一個屏幕空白幾秒鐘;我不明白髮生了什麼。耐心地盯着幾秒鐘後,個人手機屏幕上裝滿了美味的蛋糕。當我點擊其中一個查看其詳細信息時,我獲得了一個醜陋的彈出窗口,要求我安裝一個Android應用程序,這樣我就能夠在訂購蛋糕時得到絲般順暢的體驗。java
那使人失望。個人良心不容許我點擊「安裝」按鈕。我想作的就是點一塊小蛋糕而後走開。web
我點擊了彈出窗口右側的十字圖標,儘快擺脫它。但隨後安裝彈出窗口位於屏幕底部,佔據了四分之一的空間。隨着片狀UI的向下滾動是一個挑戰。我不知何故設法訂購了荷蘭蛋糕。數據庫
在經歷了這種可怕的經歷後,個人下一個挑戰是爲我爸爸訂購一件襯衫。和之前同樣,我在谷歌搜索襯衫。我點擊了第一個連接,眨眼間,整個內容就在我面前。滾動很順利。沒有安裝彈窗。我以爲好像在瀏覽本機應用程序。有一段時間我互聯網斷開了鏈接,但我仍然可以看到內容而不是恐龍遊戲。即便有個人網絡,我仍是爲父親訂購了一件襯衫和牛仔褲。最使人驚訝的是,我收到了有關訂單的通知。npm
我會稱之爲絲般順暢的體驗。這些人作得對。每一個網站都應該爲他們的用戶作。它被稱爲漸進式網絡應用程序PWA。json
正如Alex Russell所說one of his blog posts:後端
「It happens on the web from time to time that powerful technologies come to exist without the benefit of marketing departments or slick packaging. They linger and grow at the peripheries, becoming old-hat to a tiny group while remaining nearly invisible to everyone else. Until someone names them.」api
漸進式Web應用程序(PWA)更像是一種涉及技術組合的方法,可用於製做功能強大的Web應用程序。隨着用戶體驗的改善,人們將花費更多時間在網站上並看到更多廣告。 他們傾向於購買更多,而且通知更新,他們更有可能常常訪問。英國「金融時報」在2011年放棄了其原生應用程序,並使用當時可用的最佳技術構建了一個Web應用程序。 如今,該產品已發展成爲一個成熟的PWA。promise
可是,畢竟這一次,爲何當原生應用程序很好完成這項工做時,你會構建一個Web應用程序嗎?
咱們來看看Google IO 17中分享的一些指標。
五十億臺設備鏈接到網絡,使網絡成爲計算曆史上最大的平臺。在移動網絡上,每個月有1140萬獨立訪問者訪問前1000個網站,400萬訪問前千名應用。移動網絡的用戶數量是原生應用程序的四倍。可是,這個數字在交互方面急劇降低。
用戶在原生應用程序中平均花費188.6分鐘,在移動網絡上花費僅9.3分鐘。原生應用程序利用操做系統的強大功能發送推送通知,爲用戶提供重要更新。它們提供了比瀏覽器中的網站更好的用戶體驗和更快的啓動。用戶只需點擊主屏幕上的應用程序圖標,而不是在Web瀏覽器中鍵入URL。
網絡上的大多數訪問者都不太可能回來,所以開發人員提出了向他們展現彈窗以安裝本機應用程序的解決方法,以便讓他們深刻參與。可是,用戶必須完成安裝本機應用程序二進制文件的繁瑣程序。強制用戶安裝應用程序很煩人,而且進一步下降了他們首先安裝應用程序的可能性。網絡的機會很明顯。
推薦閱讀:Native And PWA: Choices, Not Challengers!
若是Web應用程序具備豐富的用戶體驗,推送通知,離線支持和即時加載,它們能夠征服世界。 這是漸進式Web應用程序的功能。
PWA提供豐富的用戶體驗,由於它具備如下幾個優點:
快速
用戶界面不古里古怪;滾動平滑,應用快速響應用戶交互
可靠
當用戶鏈接服務器繁忙的時候,一個普通的網站迫使用戶等待,什麼都不作。可是,PWA從緩存中即時加載數據。即便在2G鏈接上,PWA也能夠無縫工做。每一個獲取資源或數據的網絡請求都經過service worker(稍後會詳細介紹),該service worker首先驗證特定請求的響應是否已經在緩存中。當用戶幾乎當即得到真實內容時,即便鏈接不良,他們也會更加信任應用並將其視爲更可靠。
參與度
PWA能夠在用戶的主屏幕上得到一個位置。 它經過提供全屏工做區提供原生應用程序般的體驗。 它利用推送通知來保持用戶參與。
如今咱們知道PWA帶來了什麼,讓咱們深刻了解什麼使PWA優於原生應用程序。PWA使用service worker,Web app manifests,web push notification和用於緩存的IndexedDB /本地數據結構等技術構建。 讓咱們詳細研究一下。
service worker是一個在後臺運行的JavaScript文件,不會干擾用戶的交互。 對服務器的全部GET請求都經過service worker進行。它就像一個客戶端代理。 經過攔截網絡請求,它能夠徹底控制發送回客戶端的響應。PWA當即加載,由於service worker經過響應來自緩存的數據來消除對網絡的依賴性。
service worker只能攔截其範圍內的網絡請求。 例如,根範圍的service worker能夠攔截來自網頁的全部提取請求。 service worker做爲事件驅動系統運行。 它在不須要時進入休眠狀態,從而節省了內存。 要在Web應用程序中使用service worker,咱們首先必須使用JavaScript在頁面上註冊它。
(function main () {
/* navigator is a WEB API that allows scripts to register themselves and carry out their activities. */
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported in your browser')
/* register method takes in the path of service worker file and returns a promises, which returns the registration object */
navigator.serviceWorker.register('./service-worker.js').then (registration => {
console.log('Service Worker is registered!')
})
} else {
console.log('Service Worker is not supported in your browser')
}
})()
複製代碼
咱們首先檢查瀏覽器是否支持service worker。要在Web應用程序中註冊service worker,咱們將其URL做爲註冊函數的參數提供,可在navigator.serviceWorker中找到(navigator是一個容許腳本自行註冊並執行其活動的Web API)。 service worker只註冊一次。 每次加載頁面時都不會進行註冊。僅當現有激活的service worker與較新的service worker之間存在字節差別或者其URL已更改時,瀏覽器纔會下載service worker文件(./service-worker.js)。
上述service worker將攔截來自根(/)的全部請求。 爲了限制service worker的範圍,咱們將傳遞一個可選參數,其中一個鍵做爲範圍。
if ('serviceWorker' in navigator) {
/* register method takes in an optional second parameter as an object. To restrict the scope of a service worker, the scope should be provided. scope: '/books' will intercept requests with '/books' in the url. */
navigator.serviceWorker.register('./service-worker.js', { scope: '/books' }).then(registration => {
console.log('Service Worker for scope /books is registered', registration)
})
}
複製代碼
上面的service worker將攔截在URL中具備/ books的請求。 例如,它不會攔截/ products的請求,但它能夠很好地攔截/ books / products的請求。
如上所述,service worker做爲事件驅動系統運行。 它偵聽事件(安裝,激活,獲取,推送),並相應地調用相應的事件處理程序。 其中一些事件是service worker生命週期的一部分,它按順序經過這些事件來激活。
成功註冊服務工做程序後,將觸發安裝事件。 這是進行初始化工做的好地方,好比在IndexedDB中設置緩存或建立對象存儲。(一旦咱們瞭解了它的細節,IndexedDB會對你更有意義。如今,咱們能夠說它是一個鍵值對結構。)
self.addEventListener('install', (event) => {
let CACHE_NAME = 'xyz-cache'
let urlsToCache = [
'/',
'/styles/main.css',
'/scripts/bundle.js'
]
event.waitUntil(
/* open method available on caches, takes in the name of cache as the first parameter. It returns a promise that resolves to the instance of cache All the URLS above can be added to cache using the addAll method. */
caches.open(CACHE_NAME)
.then (cache => cache.addAll(urlsToCache))
)
})
複製代碼
在這裏,咱們正在緩存一些文件,以便下一次加載是即時的。 self指的是service worker實例。event.waitUntil使service worker等待,直到其中的全部代碼都完成執行。
一旦安裝了service worker,它就沒法監聽獲取請求。 相反,會觸發一個activate事件。若是沒有激活的service worker在同一範圍內的網站上運行,則會當即激活已安裝的service worker。可是,若是網站已有激活的service worker,則會延遲激活新service worker,直到關閉在舊service worker程序上運行的全部選項卡。 這是有道理的,由於舊的service worker可能正在使用如今在較新的服務器中修改的緩存實例。 所以,激活步驟是擺脫舊緩存的好地方。
self.addEventListener('activate', (event) => {
let cacheWhitelist = ['products-v2'] // products-v2 is the name of the new cache
event.waitUntil(
caches.keys().then (cacheNames => {
return Promise.all(
cacheNames.map( cacheName => {
/* Deleting all the caches except the ones that are in cacheWhitelist array */
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName)
}
})
)
})
)
})
複製代碼
在上面的代碼中,咱們刪除舊的緩存。 若是緩存的名稱與cacheWhitelist不匹配,則將其刪除。要跳過等待階段並當即激活service worker,咱們使用skip.waiting()。
self.addEventListener('activate', (event) => {
self.skipWaiting()
// The usual stuff
})
複製代碼
一旦激活了service worker,它就能夠監聽獲取請求和推送事件。
只要網頁經過網絡觸發對資源的獲取請求,就會調用來自service worker的fetch事件。 fetch事件處理程序首先在緩存中查找請求的資源。若是它存在於緩存中,則它返回具備緩存資源的響應。 不然,它會向服務器發起一個獲取請求,當服務器發回帶有請求資源的響應時,它會將其放入緩存中以供後續請求使用。
/* Fetch event handler for responding to GET requests with the cached assets */
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('products-v2')
.then (cache => {
/* Checking if the request is already present in the cache. If it is present, sending it directly to the client */
return cache.match(event.request).then (response => {
if (response) {
console.log('Cache hit! Fetching response from cache', event.request.url)
return response
}
/* If the request is not present in the cache, we fetch it from the server and then put it in cache for subsequent requests. */
fetch(event.request).then (response => {
cache.put(event.request, response.clone())
return response
})
})
})
)
})
複製代碼
event.respondWith讓service worker向客戶端發送一個自定義響應。
離線優先如今是一件事。 對於任何非關鍵請求,咱們必須提供來自緩存的響應,而不是去請求服務器。若是緩存中沒有任何資源,咱們從服務器獲取它,而後將其緩存以用於後續請求。
service worker只能在HTTPS網站上工做,由於他們有權操縱任何獲取請求的響應。 有惡意的人可能會篡改HTTP網站上的請求響應。所以,在HTTPS上託管PWA是強制性的。 service worker不會中斷DOM的正常運行。 他們沒法直接與網頁通訊。 要將任何消息發送到網頁,它會使用發佈消息。
假設您正在忙着在手機上玩遊戲,並會彈出一條通知,告訴您本身喜歡的品牌可享受30%的折扣。沒有任何進一步的麻煩,你點擊通知,而後屏住呼吸。 在用戶使用產品時,獲取板球或足球比賽的實時更新或將重要的電子郵件和提醒做爲通知是一件大事。此功能僅在原生應用程序中可用,直到PWA出現。 PWA利用Web推送通知來競爭原生應用程序提供的強大功能。即便在任何瀏覽器選項卡中未打開PWA,即便瀏覽器未打開,用戶仍會收到Web推送通知。
Web應用程序必需要求用戶容許向其發送推送通知。
{
"endpoint": "https://fcm.googleapis.com/fcm/send/c7Veb8VpyM0:APA91bGnMFx8GIxf__UVy6vJ-n9i728CUJSR1UHBPAKOCE_SrwgyP2N8jL4MBXf8NxIqW6NCCBg01u8c5fcY0kIZvxpDjSBA75sVz64OocQ-DisAWoW7PpTge3SwvQAx5zl_45aAXuvS",
"expirationTime": null,
"keys": {
"p256dh": "BJsj63kz8RPZe8Lv1uu-6VSzT12RjxtWyWCzfa18RZ0-8sc5j80pmSF1YXAj0HnnrkyIimRgLo8ohhkzNA7lX4w",
"auth": "TJXqKozSJxcWvtQasEUZpQ"
}
}
複製代碼
上述token中包含的endpoint對於每一個訂閱都是惟一的。在通常的網站上,成千上萬的用戶會贊成接收推送通知,對於每一個用戶,這個endpoint都是惟一的。所以,在此endpoint的幫助下,應用程序能夠經過向其發送推送通知來定位這些用戶。expirationTime是訂閱對特定設備有效的時間量。若是expirationTime是20天,則意味着用戶的推送訂閱將在20天后過時,而且用戶將沒法接收舊訂閱的推送通知。在這種狀況下,瀏覽器將爲該設備生成新的訂閱token。 auth和p256dh密鑰用於加密。
如今,要在未來向這些成千上萬的用戶發送推送通知,咱們首先必須保存他們各自的訂閱token。應用程序服務器(後端服務器,多是Node.js腳本)的工做是向這些用戶發送推送通知。這可能聽起來像使用請求有效負載中的通知數據向端點URL發出POST請求同樣簡單。可是,應該注意的是,若是用戶在服務器觸發了針對他們的推送通知時不在線,則他們應該在他們從新聯機後仍然會收到該通知。服務器必須處理這些場景,同時向用戶發送數千個請求。跟蹤用戶鏈接的服務器聽起來很複雜。所以,中間的某些東西將負責將Web推送通知從服務器路由到客戶端。這稱爲推送服務,每一個瀏覽器都有本身的推送服務實現。瀏覽器必須告知推送服務如下信息才能發送任何通知:
生命週期
這是消息應排隊的時間長度,以防它未傳遞給用戶。 一旦這段時間過去,消息將從隊列中刪除。
消息緊迫性
這樣推送服務經過僅發送高優先級消息來保留用戶的電量。
推送服務將消息路由到客戶端。 由於即便其各自的Web應用程序未在瀏覽器中打開,客戶端也必須接收推送,所以必須經過在後臺持續監視的內容來監聽推送事件。 你猜對了:這是service worker的工做。 service worker偵聽推送事件並執行向用戶顯示通知的工做。
所以,如今咱們知道瀏覽器,推送服務,service worker和應用服務器協同工做以向用戶發送推送通知。 咱們來看看實現細節。
詢問用戶的許但是一次性的事情。 若是用戶已經授予接收推送通知的權限,咱們不該再訊問。 權限值保存在Notification.permission中。
/* Notification.permission can have one of these three values: default, granted or denied. */
if (Notification.permission === 'default') {
/* The Notification.requestPermission() method shows a notification permission prompt to the user. It returns a promise that resolves to the value of permission*/
Notification.requestPermission().then (result => {
if (result === 'denied') {
console.log('Permission denied')
return
}
if (result === 'granted') {
console.log('Permission granted')
/* This means the user has clicked the Allow button. We’re to get the subscription token generated by the browser and store it in our database. The subscription token can be fetched using the getSubscription method available on pushManager of the serviceWorkerRegistration object. If subscription is not available, we subscribe using the subscribe method available on pushManager. The subscribe method takes in an object. */
serviceWorkerRegistration.pushManager.getSubscription()
.then (subscription => {
if (!subscription) {
const applicationServerKey = ''
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true, // All push notifications from server should be displayed to the user
applicationServerKey // VAPID Public key
})
} else {
saveSubscriptionInDB(subscription, userId) // A method to save subscription token in the database
}
})
}
})
}
複製代碼
在上面的訂閱方法中,咱們傳遞userVisibleOnly和applicationServerKey來生成訂閱token。userVisibleOnly屬性應始終爲true,由於它告訴瀏覽器服務器發送的任何推送通知都將顯示給客戶端。 要了解applicationServerKey的用途,讓咱們考慮一個場景。
若是有人得到了數千個訂閱token,他們能夠很好地向這些訂閱中包含的端點發送通知。 端點沒法連接到您的惟一標識。爲了向Web應用程序上生成的訂閱token提供惟一標識,咱們使用VAPID協議。使用VAPID,應用程序服務器在發送推送通知時自動向推送服務標識本身。 咱們生成兩個鍵,以下所示:
const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()
複製代碼
web-push是一個npm模塊,vapidKeys將擁有一個公鑰和一個私鑰。 上面使用的應用程序服務器密鑰是公鑰。
Web推送服務器(應用程序服務器)的工做很是簡單。 它向訂閱令牌發送通知有效負載。
const options = {
TTL: 24*60*60, //TTL is the time to live, the time that the notification will be queued in the push service
vapidDetails: {
subject: 'email@example.com',
publicKey: '',
privateKey: ''
}
}
const data = {
title: 'Update',
body: 'Notification sent by the server'
}
webpush.sendNotification(subscription, data, options)
複製代碼
它使用Web推送庫中的sendNotification方法。
service worker向用戶顯示通知:
self.addEventListener('push', (event) => {
let options = {
body: event.data.body,
icon: 'images/example.png',
}
event.waitUntil(
/* The showNotification method is available on the registration object of the service worker. The first parameter to showNotification method is the title of notification, and the second parameter is an object */
self.registration.showNotification(event.data.title, options)
)
})
複製代碼
到目前爲止,咱們已經看到service worker如何利用緩存來存儲請求並使PWA快速可靠,咱們已經看到了Web推送通知如何讓用戶參與其中。
要在客戶端存儲大量數據以供離線支持,咱們須要一個巨大的數據結構。 讓咱們來看看金融時報的PWA。你必須親眼目擊這種數據結構的強大功能。 在瀏覽器中加載URL,而後關閉Internet鏈接。從新加載頁面。爾加! 它還在運做嗎? 它是。 (就像我說的,離線是新的黑色。)數據不是來自網絡。 它正在從緩存裏供應。 轉到Chrome開發者工具的「Application」標籤。 在「Storage」下,你將找到「IndexedDB」。
查看「文章」對象庫,並展開任何項目以查看本身的魔力。 英國「金融時報」已存儲此數據以供離線支持。這種容許咱們存儲大量數據的數據結構稱爲IndexedDB。 IndexedDB是一個基於JavaScript的面向對象的數據庫,用於存儲結構化數據。 咱們能夠在此數據庫中建立不一樣的對象存儲用於各類目的。 例如,正如咱們在上圖中看到的那樣,「Resources」,「ArticleImages」和「Articles」被稱爲對象存儲。對象存儲中的每一個記錄都使用密鑰進行惟一標識。 IndexedDB甚至能夠用於存儲文件和blob。
讓咱們嘗試經過建立用於存儲書籍的數據庫來理解IndexedDB。
let openIdbRequest = window.indexedDB.open('booksdb', 1)
若是數據庫booksdb尚不存在,則上面的代碼將建立booksdb數據庫。 open方法的第二個參數是數據庫的版本。指定版本會處理未來可能發生的與架構相關的更改。例如,booksdb如今只有一個表,可是當應用程序增加時,咱們打算再添加兩個表。 爲了確保咱們的數據庫與更新的模式同步,咱們將指定比前一個更高的版本。
調用open方法不會當即打開數據庫。 這是一個返回IDBOpenDBRequest對象的異步請求。 該對象具備成功和錯誤屬性;咱們必須爲這些屬性編寫適當的處理程序來管理鏈接的狀態。
let dbInstance
openIdbRequest.onsuccess = (event) => {
dbInstance = event.target.result
console.log('booksdb is opened successfully')
}
openIdbRequest.onerror = (event) => {
console.log(’There was an error in opening booksdb database')
}
openIdbRequest.onupgradeneeded = (event) => {
let db = event.target.result
let objectstore = db.createObjectStore('books', { keyPath: 'id' })
}
複製代碼
要管理對象存儲的建立或修改(對象存儲相似於基於SQL的表 -它們具備鍵值結構),則在openIdbRequest對象上調用onupgradeneeded方法。 只要版本更改,就會調用onupgradeneeded方法。 在上面的代碼片斷中,咱們建立了一個使用惟一鍵做爲ID的書籍對象庫。
讓咱們說,在部署這段代碼以後,咱們必須再建立一個對象存儲,users
。 因此,如今咱們的數據庫版本將是2。
let openIdbRequest = window.indexedDB.open('booksdb', 2) // New Version - 2
/* Success and error event handlers remain the same. The onupgradeneeded method gets called when the version of the database changes. */
openIdbRequest.onupgradeneeded = (event) => {
let db = event.target.result
if (!db.objectStoreNames.contains('books')) {
let objectstore = db.createObjectStore('books', { keyPath: 'id' })
}
let oldVersion = event.oldVersion
let newVersion = event.newVersion
/* The users tables should be added for version 2. If the existing version is 1, it will be upgraded to 2, and the users object store will be created. */
if (oldVersion === 1) {
db.createObjectStore('users', { keyPath: 'id' })
}
}
複製代碼
咱們在打開請求的成功事件處理程序中緩存了dbInstance。 要在IndexedDB中檢索或添加數據,咱們將使用dbInstance。讓咱們在圖書對象商店中添加一些圖書記錄。
let transaction = dbInstance.transaction('books')
let objectstore = dbInstance.objectstore('books')
let bookRecord = {
id: '1',
name: ’The Alchemist',
author: 'Paulo Coelho'
}
let addBookRequest = objectstore.add(bookRecord)
addBookRequest.onsuccess = (event) => {
console.log('Book record added successfully')
}
addBookRequest.onerror = (event) => {
console.log(’There was an error in adding book record')
}
複製代碼
咱們使用transactions
,特別是在對象存儲上寫入記錄時。 事務只是確保數據完整性的操做的包裝器。若是事務中的任何操做失敗,則不對數據庫執行任何操做。
讓咱們用put方法修改一本書記錄:
let modifyBookRequest = objectstore.put(bookRecord) // put method takes in an object as the parameter
modifyBookRequest.onsuccess = (event) => {
console.log('Book record updated successfully')
}
複製代碼
讓咱們用get方法檢索一本書記錄:
let transaction = dbInstance.transaction('books')
let objectstore = dbInstance.objectstore('books')
/* get method takes in the id of the record */
let getBookRequest = objectstore.get(1)
getBookRequest.onsuccess = (event) => {
/* event.target.result contains the matched record */
console.log('Book record', event.target.result)
}
getBookRequest.onerror = (event) => {
console.log('Error while retrieving the book record.')
}
複製代碼
既然PWA和原生應用程序之間幾乎沒有任何區別,那麼爲PWA提供一個主要位置是有意義的。若是您的網站符合PWA的基本標準(託管在HTTPS上,與service worker集成並具備manifest.json),而且在用戶花了一些時間在網頁上以後,瀏覽器將在底部調用提示,詢問 用戶將應用程序添加到其主屏幕,以下所示:
PWA使用manifest.json來提供此功能。 讓咱們看一個簡單的manifest.json文件。
{
"name": "Demo PWA",
"short_name": "Demo",
"start_url": "/?standalone",
"background_color": "#9F0C3F",
"theme_color": "#fff1e0",
"display": "standalone",
"icons": [{
"src": "/lib/img/icons/xxhdpi.png?v2",
"sizes": "192x192"
}]
}
複製代碼
short_name顯示在用戶的主屏幕和系統設置中。該名稱將顯示在Chrome提示符和啓動屏幕上。啓動畫面是用戶在應用程序準備啓動時看到的內容。 start_url是您應用的主屏幕。這是用戶點擊主屏幕上的圖標時得到的內容。 background_color用於初始屏幕。 theme_color設置工具欄的顏色。顯示模式的獨立值表示應用程序將以全屏模式運行(隱藏瀏覽器的工具欄)。當用戶安裝PWA時,其大小僅爲千字節,而不是兆字節的原生應用程序。
service worker,Web推送通知,IndexedDB和主屏幕位置彌補了離線支持,可靠性和參與度。應該注意的是,service worker沒有激活而且在第一次加載時開始工做。在緩存全部靜態資產和其餘資源以前,第一個加載仍然很慢。咱們能夠實施一些策略來優化第一次加載。
全部資源,包括HTML,樣式表,圖像和JavaScript,都將從服務器中獲取。 文件越多,獲取它們所需的HTTPS請求就越多。 咱們可使用像WebPack這樣的打包工具打包咱們的靜態資源,從而減小對服務器的HTTP請求數量。WebPack經過使用諸如代碼分割之類的技術(即僅打包當前頁面加載所需的那些文件,而不是將全部這些文件打包在一塊兒)和treeShaking(即刪除重複的依賴項或 導入但未在代碼中使用的依賴項)。
網絡延遲的主要緣由之一是網絡延遲。一個字節從A行進到B所需的時間因網絡鏈接而異。例如,經過Wi-Fi進行的特定往返行程在3G鏈接上須要50毫秒和500毫秒,而在2G鏈接上須要2500毫秒。這些請求是使用HTTP協議發送的,這意味着當某個特定鏈接用於請求時,在提供前一個請求的響應以前,它不能用於任何其餘請求。一個網站一次能夠發出六個異步HTTP請求,由於網站可使用六個鏈接來發出HTTP請求。通常的網站提出大約100個請求;所以,若是最多有六個鏈接可用,用戶最終可能會在一次往返中花費大約833毫秒。 (計算是833毫秒 - 100/6 = 1666.咱們必須將1666除以2,由於咱們正在計算往返時間。)在HTTP2到位的狀況下,週轉時間大大縮短。HTTP2不會阻止鏈接頭,所以能夠同時發送多個請求。
大多數HTTP響應包含last-modified
和Etag
標頭。last-modified
的標頭是上次修改文件的日期,Etag
是基於文件內容的惟一值。只有在更改文件內容時纔會更改它。若是緩存版本已在本地可用,則可使用這兩個標頭來避免再次下載文件。若是瀏覽器在本地提供此文件的版本,則能夠在請求中添加如下兩個標頭中的任何一個:
如今的狀況是快速響應,但咱們的工做還沒完,咱們任然須要去解析HTML,加載css樣式,使頁面可交互。顯示一些空元素盒子是有必要的,而不是一個blank屏幕。當HTML文檔開始解析,當遇到<script src='asset.js'></script>
,他將發起一個異步請求到服務器獲取asset.js
,這時,整個渲染進程將會被阻塞直到返回數據。想象一下若是有不少異步獲取靜態資源請求,在script
標籤中使用async
將會是個好的選擇,例如:<script src='asset.js' async></script>.
經過在此處引入async關鍵字,瀏覽器將發出異步請求以獲取asset.js,而不會妨礙HTML的解析。若是稍後須要腳本文件,咱們能夠推遲下載該文件,直到整個HTML被解析爲止。 可使用defer關鍵字來延遲腳本文件,例如<script src ='asset.js'defer> </ script>
。
咱們已經學到了不少新東西,這些東西都是很酷的Web應用程序。 如下是咱們在本文中探討的全部內容的摘要:
service worker
充分利用緩存來加速資源的加載。Web
推送通知在引擎蓋下工做。IndexedDB
來存儲大量數據。HTTP2
和添加headers標籤,如Etag
,last-modified
和If-None-Match
,可防止下載有效的緩存資源。