前端性能優(yōu)化原理與實踐(二)

摘自前端性能優(yōu)化原理與實踐

從 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 StorageSession StorageAPI方面無異,這里我們以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 storeobject 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 上跑一遍”

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 預約了下午三點到四點去地稅局辦事,因手頭上有事,就想著晚點去,反正也就7.8分鐘距離。三點半了我整理資料準備發(fā)現(xiàn)有...
    繁星月夜閱讀 404評論 0 0
  • 兩種基本的數(shù)據(jù)訪問途徑:全掃描或者索引掃描。 上面這個例子展示了基于數(shù)據(jù)存儲方式的不同優(yōu)化器的執(zhí)行計劃選擇也可能不...
    貓貓_tomluo閱讀 485評論 0 1
  • 一個老木匠提出退休,他對老板說,離開崗位后,就去享天倫之樂。 老板舍不得這位對企業(yè)發(fā)展作出了貢獻的好工人,請他幫助...
    周周淼淼閱讀 196評論 0 0
  • 一個90后老阿姨的笑點世界,惡搞是我的繩命 【來做題】 一天早上,你收到了來自前任的婚禮邀請。 這個時候,你___...
    素菜包閱讀 653評論 9 12

友情鏈接更多精彩內(nèi)容