聊一聊常见的浏览器端数据存储方案

javascript/jquery

浏览数:165

2019-3-9

前言:

五一假期在撸代码的时候用到cookie,感觉对浏览器的数据存储方案不是很了解,因此又去翻了两本大头书中间的关于浏览器端数据存储的章节,同时去MDN逛了逛,又看了几篇文章,算是对浏览器的数据存储方案有了一个了解,在此总结一下!

浏览器存储

在浏览器端存储数据对我们是很有用,这相当于赋予浏览器记忆的功能,可以纪录用户的所有状态信息,增强用户体验。比如当纪录用户的登陆状态时,可以让用户能够更快的进行访问,而不是每次登陆时都需要去进行繁琐的操作。

总的来说,现在市面上最常见的数据存储方案是以下三种:

  • Cookie
  • web存储 (localStorage和seesionStorage)
  • IndexedDB

图片.png

Cookie

Cookie的又称是HTTP Cookie,最初是在客户端用于存储会话信息,从底层来看,它作为HTTP协议的一种扩展实现,Cookie数据会自动在web浏览器和web服务器之间传输,因此在服务器端脚本就可以读写存储的cookie的值,因此Cookie通常用于存储一些通用的数据,比如用户的登陆状态,首选项等。虽然随着时代的进步,HTML5所提供的web存储机制已经逐步替代了Cookie,但有些较为老的浏览器还是不兼容web存储机制,因此正处于这个老旧更替阶段的我们对于它还是要了解了解的。(比如我这个瓜皮还在用它,2333)

Cookie的优点

首先由于操作Cookie的API很早就已经定义和实现了,因此相比于其他的数据存储方式,Cookie的兼容性非常的好,兼容现在市面上所有的主流浏览器,我们在使用它的时候完全不用担心兼容问题。

Cookie的缺点

说到Cookie的缺点,那就有点多了,不然也不会在Cookie后面出现web存储等新的数据存储的方案了。
总结起来Cookie的缺点主要是以下几点:

  1. 存储量小。虽不同浏览器的存储量不同,但基本上都是在4kb左右。
  2. 影响性能。由于Cookie会由浏览器作为请求头发送,因此当Cookie存储信息过多时,会影响特定域的资源获取的效率,增加文档传输的负载。
  3. 只能储存字符串。
  4. 安全问题。存储在Cookie的任何数据可以被他人访问,因此不能在Cookie中储存重要的信息。
  5. 由于第三方Cookie的滥用,所以很多老司机在浏览网页时会禁用Cookie,所以我们不得不测试用户是否支持Cookie,这也是很麻烦的一件事。

Cookie的操作

基本的Cookie操作主要有三个:读取,写入和删除。但在JavaScript中去处理cookie是一件很繁琐的事情,因为cookie中的所有的名字和值都是经过URI编码的,所以当我们必须使用decodeURICompoent来进行解码才能得到cookie的值。我们来看看CookieUtil对象是如何操纵cookie的:

var CookieUtil = {
    // get可根据cookie的名字获取相应的值
    get: function() {
        const cookieName = encodeURIcOMPONET(name) + "=",
               cookieStart = document.cookie.indexOf(cookieName),
               cookieValue = null
        if(cookieStart > -1) {
            const cookieEnd = document.cookie.indexOf(";", cookieStart)
            if(cookieEnd == -1) {
                cookieEnd = document.cookie.length
            }
            cookieValue = decodeURICompoent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd))  
        }
        return cookieValue
    }
    // set设置一个cookie
    set: function(name, value, expires, path, domain, secure) {
        var cookieText = encodeURIComponet(name)+"="+encodeURIComponet(value)
        if(expires instanceof Date) {
            cookieText += "; expires=" + expires.toGMTString()
        }
        if(path) {
            cookieText += ";path=" + path
        }
        if(domain) {
            cookieText += "; domain" + domain
        }
        if(secure) {
            cookieText += "; secure"
        }
        document.cookie = cookieText
    }
    // 删除已有的cookie
    unset: function(name, path, domain, secure) {
        this.set(name, "", new Date(0), path, domain, secure)
    }
}

是不是很麻烦,无论是获取一个cookie的值或是设置一个cookie都是很麻烦的事情,这也成为了后续的浏览器数据存储方案出现的一大原因。

web存储

web存储机制最初作为HTML5的一部分被定义成API的形式,但又由于其本身的独特性与其他的一些原因而剥离了出来,成为独立的一个标准。web存储标准的API包括locaStorage对象和seesionStorage对象。它所产生的主要原因主要出于以下两个原因:

  • 人们希望有一种在cookie之外存储回话数据的途径。
  • 人们希望有一种存储大量可以跨会话存在的数据的机制。

(注:其实在最初的web存储规范中包含了两种对象的定义:seesionStorage和globalStorage,这两个对象在支持这两个对象的浏览器中都是以windows对象属性的形式存在的)

localStorage

localStorage对象在修订过的HTML5规范中作为持久保存客户端数据的方案取代了我们上面所提到的globalStorage。从功能上来讲,我们可以通过locaStorage在浏览器端存储键值对数据,它相比于cookie而言,提供了更为直观的API,且在安全上相对好一点
,而且虽然localStorage只能存储字符串,但它也可以存储字符串化的JSON数据,因此相比于cookie,localStorage能存储更复杂的数据。总的来说相较于cookie,localStorage有以下优势:

  • 提供了简单明了的API来进行操作
  • 更加安全
  • 可储存的数据量更大

也正是出于以上这些原因,localStorage被视为替代cookie的解决方案,但还是要注意不要在localStorage中存储敏感信息。

localStorage的基本语法

localStorage的基本操作很简单,示例如下:

// 使用方法存储数据
localStorage.setItem("name", "Srtian")
// 使用属性存储数据
localStorage.say = "Hello world"
// 使用方法读取数据
const name = localStorage.getItem("name")
// 使用属性读取数据
const say = localStorage.say
// 删除数据
localStorage.removeItem("name")

但需要注意的是,我们上面的示例全是存储字符串格式的数据,当我们需要传输其他格式的数据时,我们就需要将这些数据全部转换为字符串格式,然后再进行存储:

const user = {name:"Srtian", age: 22}
localStorage.setItem("user", JSON.stringify(user))

当然,我们在获取值的时候也别忘了将其转化回来:

var age = JSON.parse(localStorage.user)

localStorage储存数据的有效期与作用域

通过localStorage存储的数据时永久性的,除非我们使用removeItem来删除或者用户通过设置浏览器配置来删除,负责数据会一直保留在用户的电脑上,永不过期。

localStorage的作用域限定在文档源级别的(意思就是同源的才能共享),同源的文档间会共享localStorage的数据,他们可以互相读取对方的数据,甚至有时会覆盖对方的数据。当然,localStorage的作用域同样也受浏览器的限制。

localStorage的兼容

localStorage的兼容如下表所示:

    Feature     Chrome  Edge    Firefox (Gecko) Internet Explorer   Opera   Safari (WebKit)
localStorage    4   (Yes)      3.5              8                10.50     4
sessionStorage  5   (Yes)      2                8                10.50     4

sessionStorage

sessionStorage是web存储机制的另一大对象,sessionStorage 属性允许我们去访问一个 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage里面存储的数据没有过期时间设置,而Session Storage只存储当前会话页的数据,且只有当用户关闭当前会话页或浏览器时,数据才会被清除。

sessionStorage的基本语法

我们可以通过下面的语法,来保存,获取,删除数据,大体语法与:

// 保存数据到sessionStorage
sessionStorage.setItem('name', 'Srtian');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('name');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('name');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();

下面的示例会自动保存一个文本输入框的内容,如果浏览器因偶然因素被刷新了,文本输入框里面的内容会被恢复,写入的内容不会丢失:

// 获取文本输入框
var field = document.getElementById("field")

// 检测是否存在 autosave 键值
// (这个会在页面偶然被刷新的情况下存在)
if (sessionStorage.getItem("autosave")) {
  // 恢复文本输入框的内容
  field.value = sessionStorage.getItem("autosave")
}
// 监听文本输入框的 change 事件
field.addEventListener("change", function() {
  // 保存结果到 sessionStorage 对象中
  sessionStorage.setItem("autosave", field.value)
})

在兼容性和优点方面,sessionStorage和localStorage是差不多的,因此在此也就不多说了,下面我们来聊一聊IndexedDB。

IndexedDB

虽然web存储机制对于存储较少量的数据非常便捷好用,但对于存储更大量的结构化数据来说,这种方法就不太满足开发者们的需求了。IndexedDB就是为了应对这个需求而产生的,它是由HTML5所提供的一种本地存储,用于在浏览器中储存较大数据结构的 Web API,并提供索引功能以实现高性能查找。它一般用于保存大量用户数据并要求数据之间有搜索需要的场景,当网络断开时,用户就可以做一些离线的操作。它较之SQL更为方便,不需要写一些特定的语法对数据进行操作,数据格式是JSON。

IndexedDB的基本语法

使用IndexedDB在浏览器端存储数据会比上述的其他方法更为复杂。首先,我们需要创建数据库,并指定这个数据库的版本号:

// 注意数据库的版本号只能是整数
const request = IndexedDB.open(databasename, version)

然后我们需要生成处理函数,需要注意的是onupgradeneeded 是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间以及构建和删除索引。

request.onerror = function() {
    // 创建数据库失败时的回调函数
}
request.onsuccess = function() {
    // 创建数据库成功时的回调函数
}
request.onupgradeneededd = function(e) {
     // 当数据库改变时的回调函数
}

然后我们就可以建立对象存储空间了,对象存储空间仅调用createObjectStore()就可以创建。这个方法使用存储空间的名称,和一个对象参数。即便这个参数对象是可选的,它还是非常重要的,因为它可以让我们定义重要的可选属性和完善你希望创建的对象存储空间的类型。

request.onupgradeneeded = function(event) {
    const db = event.target.result
    const objectStore = db.createObjectStore('name', { keyPath:'id' })
}

对象的存储空间我们已经建立好了,接下来我们就可以进行一系列的骚操作了,比如来个蛇皮走位!不不不,口误口误,比如添加数据:

addData: function(db, storename, data) {
    const store = store = db.transaction(storename, 'readwrite').objectStore(storename)
    for(let i = 0; i < data.length; i++) {
        const request = store.add(data[i])
        request.onerror = function() {
            console.error('添加数据失败')
        }
        request.onsuccess = function() {
            console.log('添加数据成功')
        }
    }
}

如果我们想要修改数据,语法与添加数据差不多,因为重复添加已存在的数据会更新原本的数据,但还是有细小的差别:

putData: function(db, storename, data) {
    const store = store = db.transaction(storename, 'readwrite').objectStore(storename)
    for(let i = 0; i < data.length; i++) {
        const request = store.put(data[i])
        request.onerror = function() {
            console.error('修改数据失败')
        }
        request.onsuccess = function() {
            console.log('修改数据成功')
        }
    }
}

获取数据:

getDataByKey: function(db, storename, key) {
    const store = store = db.transaction(storename, 'readwrite').objectStore(storename)
    const request = store.get(key)
    request.onerror = function() {
        console.error('获取数据失败')
    }
    request.onsuccess = function(e) {
        const result = e.target.result
        console.log(result)
    }
}

删除数据:

deleteDate: function(db, storename, key) {
    const store = store = db.transaction(storename, 'readwrite').objectStore(storename)
    store.delete(key)
    console.log('已删除存储空间' + storename + '中的' + key + '纪录')
}

关闭数据库:

db.close

IndexedDB的优点(相较于前面的存储方案)

  • 拥有更大的储存空间
  • 能够处理更为复杂和结构化的数据
  • 拥有更多的交互控制
  • 每个’database’中可以拥有多个’database’和’table’

IndexedDB的局限性

了解了IndexedDB的优点,我们当然也要来聊一聊IndexedDB的局限性与适用的场景:

1. 存储空间限制

一个单独的数据库项目的大小没有限制。然而可能会限制每个 IndexedDB 数据库的大小。这个限制(以及用户界面对它进行断言的方式)在各个浏览器上也可能有所不同:

2. 兼容性问题

图片.png

从上面的图我们可以看出对于IndexedDB的兼容来讲比前面所提及的存储方案要差不少,因此在使用IndexedDB时,我们也要好好的考虑兼容性的问题

3. indexedDB受同源策略的限制

indexedDB使用同源原则,这意味着它把存储空间绑定到了创建它的站点的源(典型情况下,就是站点的域或是子域),所以它不能被任何其他源访问。要着重指出的一点是 IndexedDB 不适用于从另一个站点加载进框架的内容 (不管是 <frame> 还是 <iframe>。这是一项安全措施。详情请看这个:https://bugzilla.mozilla.org/show_bug.cgi?id=595307

除此之外,IndexedDB还存在诸如:不适合存储敏感数据,相较于web存储机制的操作更加复杂等问题,这都是我们在使用IndexedDB时需要考虑的。