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

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

从 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。这种特性使得客户端渲染的源代码总是特别简洁:

<!doctype html>
<html>
  <head>
    <title>我是客户端渲染的页面</title>
  </head>
  <body>
    <div id='root'></div>
    <script src='index.js'></script>
  </body>
</html>

根节点下到底是什么内容呢?你不知道,我不知道,只有浏览器把 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: `<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)

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容

  • 预约了下午三点到四点去地税局办事,因手头上有事,就想着晚点去,反正也就7.8分钟距离。三点半了我整理资料准备发现有...
    繁星月夜阅读 313评论 0 0
  • 两种基本的数据访问途径:全扫描或者索引扫描。 上面这个例子展示了基于数据存储方式的不同优化器的执行计划选择也可能不...
    猫猫_tomluo阅读 406评论 0 1
  • 一个老木匠提出退休,他对老板说,离开岗位后,就去享天伦之乐。 老板舍不得这位对企业发展作出了贡献的好工人,请他帮助...
    周周淼淼阅读 179评论 0 0
  • 一个90后老阿姨的笑点世界,恶搞是我的绳命 【来做题】 一天早上,你收到了来自前任的婚礼邀请。 这个时候,你___...
    素菜包阅读 577评论 9 12