一、cookie
因?yàn)镠TTP請(qǐng)求無(wú)狀態(tài),所以需要cookie去維持客戶端狀態(tài)
過(guò)期時(shí)間 expire
cookie的生成方式
- http response header中的set-cookie
- js中可以通過(guò)document.cookie可以讀寫cookie
僅僅作為瀏覽器存儲(chǔ)(大小4KB左右,能力被localstorage替代)
cookie中在相關(guān)域名下面 —— cdn的流量損耗,所以我們需要將cdn與主域名獨(dú)立起來(lái)
種cookie
document.cookie = 'username=hello'
二、LocalStorage
HTML5設(shè)計(jì)出來(lái)專門用于瀏覽器存儲(chǔ)的
大小為5M左右
僅在客戶端使用,不和服務(wù)端進(jìn)行通信
接口封裝較好
瀏覽器本地緩存方案
存儲(chǔ)js文件
var testJsContent = localStorage.getItem('test')
if(testJsContent) {
eval(testJsContent)
} else {
axios.get('./test.js').then(res => {
var responseText = res
eval(responseText)
localStorage.setItem('test', responseText)
})
}
三、SessionStorage
會(huì)話級(jí)別的瀏覽器存儲(chǔ)
大小為5M左右
僅在客戶端使用,不和服務(wù)端進(jìn)行通信
接口封裝較好
對(duì)于表單信息的維護(hù)
四、IndexedDB
IndexedDB 是一種低級(jí)API,用于客戶端存儲(chǔ)大量結(jié)構(gòu)化數(shù)據(jù)。該API使用索引來(lái)實(shí)現(xiàn)對(duì)該數(shù)據(jù)的高性能搜索。雖然 Web Storage 對(duì)于存儲(chǔ)較少量的數(shù)據(jù)很有用,但對(duì)于存儲(chǔ)更大量的結(jié)構(gòu)化數(shù)據(jù)來(lái)說(shuō),這種方法不太有用。IndexedDB提供了一個(gè)解決方案。
為應(yīng)用創(chuàng)建離線版本
使用方法參考:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html
五、PWA
PWA (Progressive Web Apps) 是一種 Web App 新模型,并不是具體指某一種前沿的技術(shù)或者某一個(gè)單一的知識(shí)點(diǎn),我們從英文縮寫來(lái)看就能看出來(lái),這是一個(gè)漸進(jìn)式的 Web App,是通過(guò)一系列新的 Web 特性,配合優(yōu)秀的 UI 交互設(shè)計(jì),逐步的增強(qiáng) Web App 的用戶體驗(yàn)。
PWA優(yōu)點(diǎn):
- 可靠:在沒(méi)有網(wǎng)絡(luò)的環(huán)境中也能提供基本的頁(yè)面訪問(wèn),而不會(huì)出現(xiàn)“未連接到互聯(lián)網(wǎng)”的頁(yè)面。
- 快速:針對(duì)網(wǎng)頁(yè)渲染及網(wǎng)絡(luò)數(shù)據(jù)訪問(wèn)有較好優(yōu)化。
- 融入(Engaging):應(yīng)用可以被增加到手機(jī)桌面,并且和普通應(yīng)用一樣有全屏、推送等特性。
Manifest實(shí)現(xiàn)添加至主屏幕
index.html
<head>
<title>Minimal PWA</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" type="text/css" href="main.css">
<link rel="icon" href="/e.png" type="image/png" />
</head>
manifest.json
{
"name": "Minimal PWA", // 必填 顯示的插件名稱
"short_name": "PWA Demo", // 可選 在APP launcher和新的tab頁(yè)顯示,如果沒(méi)有設(shè)置,則使用name
"description": "The app that helps you understand PWA", //用于描述應(yīng)用
"display": "standalone", // 定義開(kāi)發(fā)人員對(duì)Web應(yīng)用程序的首選顯示模式。standalone模式會(huì)有單獨(dú)的
"start_url": "/", // 應(yīng)用啟動(dòng)時(shí)的url
"theme_color": "#313131", // 桌面圖標(biāo)的背景色
"background_color": "#313131", // 為web應(yīng)用程序預(yù)定義的背景顏色。在啟動(dòng)web應(yīng)用程序和加載應(yīng)用程序的內(nèi)容之間創(chuàng)建了一個(gè)平滑的過(guò)渡。
"icons": [ // 桌面圖標(biāo),是一個(gè)數(shù)組
{
"src": "icon/lowres.webp",
"sizes": "48x48", // 以空格分隔的圖片尺寸
"type": "image/webp" // 幫助userAgent快速排除不支持的類型
},
{
"src": "icon/lowres",
"sizes": "48x48"
},
{
"src": "icon/hd_hi.ico",
"sizes": "72x72 96x96 128x128 256x256"
},
{
"src": "icon/hd_hi.svg",
"sizes": "72x72"
}
]
}
Manifest參考文檔:https://developer.mozilla.org/zh-CN/docs/Web/Manifest
可以打開(kāi)網(wǎng)站https://developers.google.cn/web/showcase/2015/chrome-dev-summit查看添加至主屏幕的動(dòng)圖。
如果用的是安卓手機(jī),可以下載chrome瀏覽器自己操作看看
六、Service Worker
Service Worker 是一個(gè)腳本,瀏覽器獨(dú)立于當(dāng)前網(wǎng)頁(yè),將其在后臺(tái)運(yùn)行,為實(shí)現(xiàn)一些不依賴頁(yè)面或者用戶交互的特性打開(kāi)了一扇大門。在未來(lái)這些特性將包括推送消息,背景后臺(tái)同步, geofencing(地理圍欄定位),但它將推出的第一個(gè)首要特性,就是攔截和處理網(wǎng)絡(luò)請(qǐng)求的能力,包括以編程方式來(lái)管理被緩存的響應(yīng)。
參見(jiàn)文檔:https://segmentfault.com/a/1190000012353473?utm_source=tag-newest#articleHeader4
查看瀏覽器Service Worker使用情況
chrome://serviceworker-internals/ 已安裝Service Worker的詳細(xì)情況
chrome://inspect/#service-workers
Service Workers 就像介于服務(wù)器和網(wǎng)頁(yè)之間的攔截器,能夠攔截進(jìn)出的HTTP 請(qǐng)求,從而完全控制你的網(wǎng)站。
最主要的特點(diǎn):
- 在頁(yè)面中注冊(cè)并安裝成功后,運(yùn)行于瀏覽器后臺(tái),不受頁(yè)面刷新的影響,可以監(jiān)聽(tīng)和截?cái)r作用域范圍內(nèi)所有頁(yè)面的 HTTP 請(qǐng)求。
- 網(wǎng)站必須使用 HTTPS。除了使用本地開(kāi)發(fā)環(huán)境調(diào)試時(shí)(如域名使用 localhost)
運(yùn)行于瀏覽器后臺(tái),可以控制打開(kāi)的作用域范圍下所有的頁(yè)面請(qǐng)求 - 單獨(dú)的作用域范圍,單獨(dú)的運(yùn)行環(huán)境和執(zhí)行線程
- 不能操作頁(yè)面 DOM。但可以通過(guò)事件機(jī)制來(lái)處理
- 事件驅(qū)動(dòng)型服務(wù)線程
為什么要求網(wǎng)站必須是HTTPS的,大概是因?yàn)閟ervice worker權(quán)限太大能攔截所有頁(yè)面的請(qǐng)求吧,如果http的網(wǎng)站安裝service worker很容易被攻擊
瀏覽器支持情況詳見(jiàn): https://caniuse.com/#feat=serviceworkers
Service Worker生命周期如下圖:

當(dāng)用戶首次導(dǎo)航至 URL 時(shí),服務(wù)器會(huì)返回響應(yīng)的網(wǎng)頁(yè)。
- 當(dāng)你調(diào)用 register() 函數(shù)時(shí), Service Worker 開(kāi)始下載。
- 在注冊(cè)過(guò)程中,瀏覽器會(huì)下載、解析并執(zhí)行 Service Worker ()。如果在此步驟中出現(xiàn)任何錯(cuò)誤,register() 返回的 promise 都會(huì)執(zhí)行 reject 操作,并且 Service Worker 會(huì)被廢棄。
- 一旦 Service Worker 成功執(zhí)行了,install 事件就會(huì)激活
- 安裝完成,Service Worker 便會(huì)激活,并控制在其范圍內(nèi)的一切。如果生命周期中的所有事件都成功了,Service Worker 便已準(zhǔn)備就緒,隨時(shí)可以使用。
離線緩存
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello Caching World!</title>
</head>
<body>
<!-- Image -->
<img src="/images/hello.png" />
<!-- JavaScript -->
<script async src="/js/script.js"></script>
<script>
// 注冊(cè) service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
// 注冊(cè)成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function (err) {
// 注冊(cè)失敗 :(
console.log('ServiceWorker registration failed: ', err);
});
}
</script>
</body>
</html>
注:Service Worker 的注冊(cè)路徑?jīng)Q定了其 scope 默認(rèn)作用頁(yè)面的范圍。
如果 service-worker.js 是在 /sw/ 頁(yè)面路徑下,這使得該 Service Worker 默認(rèn)只會(huì)收到 頁(yè)面/sw/ 路徑下的 fetch 事件。
如果存放在網(wǎng)站的根路徑下,則將會(huì)收到該網(wǎng)站的所有 fetch 事件。
如果希望改變它的作用域,可在第二個(gè)參數(shù)設(shè)置 scope 范圍。示例中將其改為了根目錄,即對(duì)整個(gè)站點(diǎn)生效。
service-worker.js
var cacheName = 'helloWorld'; // 緩存的名稱
// install 事件,它發(fā)生在瀏覽器安裝并注冊(cè) Service Worker 時(shí)
self.addEventListener('install', event => {
/* event.waitUtil 用于在安裝成功之前執(zhí)行一些預(yù)裝邏輯
但是建議只做一些輕量級(jí)和非常重要資源的緩存,減少安裝失敗的概率
安裝成功后 ServiceWorker 狀態(tài)會(huì)從 installing 變?yōu)?installed */
event.waitUntil(
caches.open(cacheName)
.then(cache => cache.addAll([ // 如果所有的文件都成功緩存了,便會(huì)安裝完成。如果任何文件下載失敗了,那么安裝過(guò)程也會(huì)隨之失敗。
'/js/script.js',
'/images/hello.png'
]))
);
});
/**
為 fetch 事件添加一個(gè)事件監(jiān)聽(tīng)器。接下來(lái),使用 caches.match() 函數(shù)來(lái)檢查傳入的請(qǐng)求 URL 是否匹配當(dāng)前緩存中存在的任何內(nèi)容。如果存在的話,返回緩存的資源。
如果資源并不存在于緩存當(dāng)中,通過(guò)網(wǎng)絡(luò)來(lái)獲取資源,并將獲取到的資源添加到緩存中。
*/
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
var requestToCache = event.request.clone(); //
return fetch(requestToCache).then(
function (response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName)
.then(function (cache) {
cache.put(requestToCache, responseToCache);
});
return response;
})
);
});
注:為什么用request.clone()和response.clone()
需要這么做是因?yàn)閞equest和response是一個(gè)流,它只能消耗一次。因?yàn)槲覀円呀?jīng)通過(guò)緩存消耗了一次,然后發(fā)起 HTTP 請(qǐng)求還要再消耗一次,所以我們需要在此時(shí)克隆請(qǐng)求
Clone the request—a request is a stream and can only be consumed once.
serice worker實(shí)現(xiàn)消息推送
- 提示用戶并獲得他們的訂閱詳細(xì)信息
- 將這些詳細(xì)信息保存在服務(wù)器上
- 在需要時(shí)發(fā)送任何消息
步驟一和步驟二
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Progressive Times</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<script>
var endpoint;
var key;
var authSecret;
var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
// 方法很復(fù)雜,但是可以不用具體看,知識(shí)用來(lái)轉(zhuǎn)化vapidPublicKey用
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(function (registration) {
return registration.pushManager.getSubscription()
.then(function (subscription) {
if (subscription) {
return;
}
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
})
.then(function (subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
authSecret = rawAuthSecret ?
btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
endpoint = subscription.endpoint;
return fetch('./register', {
method: 'post',
headers: new Headers({
'content-type': 'application/json'
}),
body: JSON.stringify({
endpoint: subscription.endpoint,
key: key,
authSecret: authSecret,
}),
});
});
});
}).catch(function (err) {
// 注冊(cè)失敗 :(
console.log('ServiceWorker registration failed: ', err);
});
}
</script>
</body>
</html>
步驟三 服務(wù)器發(fā)送消息給service worker
app.js
const webpush = require('web-push');
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
webpush.setVapidDetails(
'mailto:contact@deanhume.com',
'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
);
app.post('/register', function (req, res) {
var endpoint = req.body.endpoint;
saveRegistrationDetails(endpoint, key, authSecret);
const pushSubscription = {
endpoint: req.body.endpoint,
keys: {
auth: req.body.authSecret,
p256dh: req.body.key
}
};
var body = 'Thank you for registering';
var iconUrl = 'https://example.com/images/homescreen.png';
// 發(fā)送 Web 推送消息
webpush.sendNotification(pushSubscription,
JSON.stringify({
msg: body,
url: 'http://localhost:3111/',
icon: iconUrl
}))
.then(result => res.sendStatus(201))
.catch(err => {
console.log(err);
});
});
app.listen(3111, function () {
console.log('Web push app listening on port 3111!')
});
service worker監(jiān)聽(tīng)push事件,將通知詳情推送給用戶
service-worker.js
self.addEventListener('push', function (event) {
// 檢查服務(wù)端是否發(fā)來(lái)了任何有效載荷數(shù)據(jù)
var payload = event.data ? JSON.parse(event.data.text()) : 'no payload';
var title = 'Progressive Times';
event.waitUntil(
// 使用提供的信息來(lái)顯示 Web 推送通知
self.registration.showNotification(title, {
body: payload.msg,
url: payload.url,
icon: payload.icon
})
);
});