從 Cookie 到 Web Storage、IndexDB
Cookie
Cookie的本職工作并非本地存儲,而是“維持狀態(tài)”。
在 Web開發(fā)的早期,人們亟需解決的一個問題就是狀態(tài)管理的問題:HTTP 協(xié)議是一個無狀態(tài)協(xié)議,服務器接收客戶端的請求,返回一個響應,故事到此就結束了,服務器并沒有記錄下關于客戶端的任何信息。那么下次請求的時候,如何讓服務器知道“我是我”呢?
在這樣的背景下,Cookie 應運而生。
Cookie說白了就是一個存儲在瀏覽器里的一個小小的文本文件,它附著在 HTTP 請求上,在瀏覽器和服務器之間“飛來飛去”。它可以攜帶用戶信息,當服務器檢查Cookie 的時候,便可以獲取到客戶端的狀態(tài)。
Cookie的性能劣勢
Cookie不夠大,Cookie是有體積上限的,它最大只能有4KB。當Cookie超過4KB時,它將面臨被裁切的命運。這樣看來,Cookie只能用來存取少量的信息。過量的
Cookie會帶來巨大的性能浪費,Cookie是緊跟域名的。我們通過響應頭里的Set-Cookie指定要存儲的Cookie值。默認情況下,domain被設置為設置Cookie頁面的主機名,我們也可以手動設置domain的值:
Set-Cookie: name=xiuyan; domain=xiuyan.me
同一個域名下的所有請求,都會攜帶Cookie。大家試想,如果我們此刻僅僅是請求一張圖片或者一個 CSS 文件,我們也要攜帶一個Cookie 跑來跑去(關鍵是Cookie里存儲的信息我現(xiàn)在并不需要),這是一件多么勞民傷財?shù)氖虑椤?code>Cookie雖然小,請求卻可以有很多,隨著請求的疊加,這樣的不必要的 Cookie帶來的開銷將是無法想象的。
Web Storage
存儲容量大:Web Storage根據(jù)瀏覽器的不同,存儲容量可以達到5-10M之間。
僅位于瀏覽器端,不與服務端發(fā)生通信。
Web Storage 核心 API 使用示例
Web Storage保存的數(shù)據(jù)內(nèi)容和Cookie一樣,是文本內(nèi)容,以鍵值對的形式存在。Local Storage 與 Session Storage在 API方面無異,這里我們以localStorage為例:
- 存儲數(shù)據(jù):
setItem()
localStorage.setItem('user_name', 'xiuyan')
- 讀取數(shù)據(jù):
getItem()
localStorage.getItem('user_name')
- 刪除某一鍵名對應的數(shù)據(jù):
removeItem()
localStorage.removeItem('user_name')
- 清空數(shù)據(jù)記錄:
clear()
localStorage.clear()
IndexDB
IndexDB是一個運行在瀏覽器上的非關系型數(shù)據(jù)庫。既然是數(shù)據(jù)庫了,那就不是5M、10M這樣小打小鬧級別了。理論上來說,IndexDB 是沒有存儲上限的(一般來說不會小于 250M)。它不僅可以存儲字符串,還可以存儲二進制數(shù)據(jù)。
- 打開/創(chuàng)建一個
IndexDB數(shù)據(jù)庫(當該數(shù)據(jù)庫不存在時,open方法會直接創(chuàng)建一個名為xiaoceDB新數(shù)據(jù)庫)。
// 后面的回調(diào)中,我們可以通過event.target.result拿到數(shù)據(jù)庫實例
let db
// 參數(shù)1位數(shù)據(jù)庫名,參數(shù)2為版本號
const request = window.indexedDB.open("xiaoceDB", 1)
// 使用IndexDB失敗時的監(jiān)聽函數(shù)
request.onerror = function(event) {
console.log('無法使用IndexDB')
}
// 成功
request.onsuccess = function(event){
// 此處就可以獲取到db實例
db = event.target.result
console.log("你打開了IndexDB")
}
- 創(chuàng)建一個
object store(object store對標到數(shù)據(jù)庫中的“表”單位)。
// onupgradeneeded事件會在初始化數(shù)據(jù)庫/版本發(fā)生更新時被調(diào)用,我們在它的監(jiān)聽函數(shù)中創(chuàng)建object store
request.onupgradeneeded = function(event){
let objectStore
// 如果同名表未被創(chuàng)建過,則新建test表
if (!db.objectStoreNames.contains('test')) {
objectStore = db.createObjectStore('test', { keyPath: 'id' })
}
}
- 構建一個事務來執(zhí)行一些數(shù)據(jù)庫操作,像增加或提取數(shù)據(jù)等。
// 創(chuàng)建事務,指定表格名稱和讀寫權限
const transaction = db.transaction(["test"],"readwrite")
// 拿到Object Store對象
const objectStore = transaction.objectStore("test")
// 向表格寫入數(shù)據(jù)
objectStore.add({id: 1, name: 'xiuyan'})
- 通過監(jiān)聽正確類型的事件以等待操作完成。
// 操作成功時的監(jiān)聽函數(shù)
transaction.oncomplete = function(event) {
console.log("操作成功")
}
// 操作失敗時的監(jiān)聽函數(shù)
transaction.onerror = function(event) {
console.log("這里有一個Error")
}
服務端渲染的運行機制
相對于服務端渲染,同學們普遍對客戶端渲染接受度更高一些,所以我們先從大家喜聞樂見的客戶端渲染說起。
客戶端渲染
客戶端渲染模式下,服務端會把渲染需要的靜態(tài)文件發(fā)送給客戶端,客戶端加載過來之后,自己在瀏覽器里跑一遍 JS,根據(jù) JS 的運行結果,生成相應的DOM。這種特性使得客戶端渲染的源代碼總是特別簡潔:
<!doctype html>
<html>
<head>
<title>我是客戶端渲染的頁面</title>
</head>
<body>
<div id='root'></div>
<script src='index.js'></script>
</body>
</html>
根節(jié)點下到底是什么內(nèi)容呢?你不知道,我不知道,只有瀏覽器把 index.js跑過一遍后才知道,這就是典型的客戶端渲染。
頁面上呈現(xiàn)的內(nèi)容,你在html源文件里里找不到——這正是它的特點。
服務端渲染
服務端渲染的模式下,當用戶第一次請求頁面時,由服務器把需要的組件或頁面渲染成HTML字符串,然后把它返回給客戶端??蛻舳四玫绞值?,是可以直接渲染然后呈現(xiàn)給用戶的HTML內(nèi)容,不需要為了生成 DOM 內(nèi)容自己再去跑一遍 JS代碼。
使用服務端渲染的網(wǎng)站,可以說是“所見即所得”,頁面上呈現(xiàn)的內(nèi)容,我們在html源文件里也能找到。
該示例直接將 Vue 實例整合進了服務端的入口文件中:
const Vue = require('vue')
// 創(chuàng)建一個express應用
const server = require('express')()
// 提取出renderer實例
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
// 編寫Vue實例(虛擬DOM節(jié)點)
const app = new Vue({
data: {
url: req.url
},
// 編寫模板HTML的內(nèi)容
template: `<div>訪問的 URL 是: {{ url }}</div>`
})
// renderToString 是把Vue實例轉化為真實DOM的關鍵方法
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
// 把渲染出來的真實DOM字符串插入HTML模板中
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
實際項目比這些復雜很多,但萬變不離其宗。強調(diào)的只有兩點:
- 一是這個
renderToString()方法; - 二是把轉化結果
“塞”進模板里的這一步。這兩個操作是服務端渲染的靈魂操作。
在虛擬 DOM橫行的當下,服務端渲染不再是早年JSP 里簡單粗暴的字符串拼接過程,它還要求這一端要具備將虛擬 DOM 轉化為真實 DOM的能力。與其說是“把 JS 在服務器上先跑一遍”,不如說是“把 Vue、React 等框架代碼先在 Node 上跑一遍”。