前端性能优化原理与实践(二)

摘自前端性能优化原理与实践

从 Cookie 到 Web Storage、IndexDB

Cookie

Cookie的本职工作并非本地存储,而是“维持状态”

Web开发的早期,人们亟需解决的一个问题就是状态管理的问题:HTTP 协议是一个无状态协议,服务器接收客户端的请求,返回一个响应,故事到此就结束了,服务器并没有记录下关于客户端的任何信息。那么下次请求的时候,如何让服务器知道“我是我”呢?

在这样的背景下,Cookie 应运而生。

Cookie说白了就是一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查Cookie 的时候,便可以获取到客户端的状态。

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里存储的信息我现在并不需要),这是一件多么劳民伤财的事情。Cookie虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie带来的开销将是无法想象的。

Web Storage

存储容量大:Web Storage根据浏览器的不同,存储容量可以达到5-10M之间。

仅位于浏览器端,不与服务端发生通信。

Web Storage 核心 API 使用示例

Web Storage保存的数据内容和Cookie一样,是文本内容,以键值对的形式存在。Local StorageSession StorageAPI方面无异,这里我们以localStorage为例:

  • 存储数据:setItem()
localStorage.setItem('user_name', 'xiuyan')
  • 读取数据:getItem()
localStorage.getItem('user_name')
  • 删除某一键名对应的数据: removeItem()
localStorage.removeItem('user_name')
  • 清空数据记录:clear()
localStorage.clear()
IndexDB

IndexDB是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是5M10M这样小打小闹级别了。理论上来说,IndexDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。

  • 打开/创建一个IndexDB数据库(当该数据库不存在时,open方法会直接创建一个名为 xiaoceDB新数据库)。
  // 后面的回调中,我们可以通过event.target.result拿到数据库实例
  let db
  // 参数1位数据库名,参数2为版本号
  const request = window.indexedDB.open("xiaoceDB", 1)
  // 使用IndexDB失败时的监听函数
  request.onerror = function(event) {
     console.log('无法使用IndexDB')
   }
  // 成功
  request.onsuccess  = function(event){
    // 此处就可以获取到db实例
    db = event.target.result
    console.log("你打开了IndexDB")
  }
  • 创建一个 object storeobject store对标到数据库中的“表”单位)。
// onupgradeneeded事件会在初始化数据库/版本发生更新时被调用,我们在它的监听函数中创建object store
request.onupgradeneeded = function(event){
  let objectStore
  // 如果同名表未被创建过,则新建test表
  if (!db.objectStoreNames.contains('test')) {
    objectStore = db.createObjectStore('test', { keyPath: 'id' })
  }
}  
  • 构建一个事务来执行一些数据库操作,像增加或提取数据等。
  // 创建事务,指定表格名称和读写权限
  const transaction = db.transaction(["test"],"readwrite")
  // 拿到Object Store对象
  const objectStore = transaction.objectStore("test")
  // 向表格写入数据
  objectStore.add({id: 1, name: 'xiuyan'})
  • 通过监听正确类型的事件以等待操作完成。
// 操作成功时的监听函数
  transaction.oncomplete = function(event) {
    console.log("操作成功")
  }
  // 操作失败时的监听函数
  transaction.onerror = function(event) {
    console.log("这里有一个Error")
  }

服务端渲染的运行机制

相对于服务端渲染,同学们普遍对客户端渲染接受度更高一些,所以我们先从大家喜闻乐见的客户端渲染说起。

客户端渲染

客户端渲染模式下,服务端会把渲染需要的静态文件发送给客户端,客户端加载过来之后,自己在浏览器里跑一遍 JS,根据 JS 的运行结果,生成相应的DOM。这种特性使得客户端渲染的源代码总是特别简洁:



  
    我是客户端渲染的页面
  
  
    

根节点下到底是什么内容呢?你不知道,我不知道,只有浏览器把 index.js跑过一遍后才知道,这就是典型的客户端渲染。

页面上呈现的内容,你在html源文件里里找不到——这正是它的特点。

服务端渲染

服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成HTML字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的HTML内容,不需要为了生成 DOM 内容自己再去跑一遍 JS代码。

使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在html源文件里也能找到。

该示例直接将 Vue 实例整合进了服务端的入口文件中:

const Vue = require('vue')
// 创建一个express应用
const server = require('express')()
// 提取出renderer实例
const renderer = require('vue-server-renderer').createRenderer()

server.get('*', (req, res) => {
  // 编写Vue实例(虚拟DOM节点)
  const app = new Vue({
    data: {
      url: req.url
    },
    // 编写模板HTML的内容
    template: `
访问的 URL 是: {{ url }}
` }) // renderToString 是把Vue实例转化为真实DOM的关键方法 renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } // 把渲染出来的真实DOM字符串插入HTML模板中 res.end(` Hello ${html} `) }) }) server.listen(8080)

实际项目比这些复杂很多,但万变不离其宗。强调的只有两点:

  • 一是这个renderToString()方法;
  • 二是把转化结果“塞”进模板里的这一步。这两个操作是服务端渲染的灵魂操作。

在虚拟 DOM横行的当下,服务端渲染不再是早年JSP 里简单粗暴的字符串拼接过程,它还要求这一端要具备将虚拟 DOM 转化为真实 DOM的能力。与其说是“把 JS 在服务器上先跑一遍”,不如说是“把 Vue、React 等框架代码先在 Node 上跑一遍”

你可能感兴趣的:(前端性能优化原理与实践(二))