对浏览器本地存储的全面理解

1,882 阅读15分钟

在Chrome浏览器的任意一个tab页面中按下f12,点击“Application”选项卡,就可以看见控制台左侧“Storage”栏中浏览器本地存储的所有方式:

Local Storage

HTML5新方法,不过IE8及以上浏览器都兼容。

window.localStorage(或直接localStorage)指向一个可被用于访问当前源( origin )的本地存储空间的 localStorage 对象。

特点:

  1. 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的;
  2. 同一个源(origin)下的localStorage对象是共享的,不能访问不同源的localStorage对象(同源策略);
  3. 存储对象是简单的键值存储,类似于对象,但是它们在页面加载时保持完整。键和值始终是字符串;
  4. 存储大小不同浏览器不一样,一般来说在5M左右,查询浏览器Storage大小
  5. 当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是同一个域名下的其他所有页面会触发storage事件;
  6. localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡;

使用方法:

可以像访问对象一样访问Storage,也可以使用其内置方法。

  • 设置:
localStorage.colorSetting = '#a4509b';
localStorage['colorSetting'] = '#a4509b';
localStorage.setItem('colorSetting', '#a4509b');
  • 读取:
let cat1 = localStorage.myCat;
let cat2 = localStorage['myCat'];
let cat3 = localStorage.getItem('myCat');
  • 删除:
// 删除某一项
localStorage.removeItem('uselessItem');
// 删除所有项
localStorage.clear();

对象属性:

只有一个只读属性:length,返回一个整数,表示存储在 Storage 对象里的数据项(data items)数量。

storage事件:

Storage对象发生变化时,会触发storage事件。
但是,导致localStorage对象发生变化的页面不会触发该事件,而是同一域名下的其他所有页面触发。IE浏览器除外,它会在所有页面触发storage事件。

如:
test.cn/a 页面(a页面)下添加事件监听:

window.addEventListener('storage', callback);

test.cn/b 页面(b页面)下改变Storage对象(增加、删除、修改):

localStorage.setItem('test', 'xxx');

则a页面会执行callback函数,而如果在a页面下改变Storage对象,则callback函数不会被执行。

回调函数中的参数event,是一个StorageEvent对象,主要有以下实用属性:

属性类型描述
keyStringStorage对象被改变(增加、删除、修改)的属性的key
oldValueAny先前被改变的值,如果是添加值,则oldValue=null
newValueAny改变后的新值,如果是删除值,则newValue=null
urlString改变Storage对象的页面的url

注:如果调用的是localStorage.clear(),则keyoldValuenewValue都是null。

Session Storage

sessionStorage 允许你访问一个,对应当前源的 session Storage 对象。它与 localStorage 相似,不同点有以下几处:

localStoragesessionStorage
存储时间永久存储,除非手动删除在页面会话结束时会被清除
对象共享协议、域名、端口号都相同的页面共享同一个localStorage对象除了协议、域名、端口号都相同外,还需要在同一窗口下,才能共享一个sessionStorage对象
storage事件其他共享localStorage的页面会触发storage事件每个标签的sessionStorage都是隔离的,因此它们无法通信。 sessionStorage的storage事件仅在同一选项卡上的其他iframe框架中触发。
  • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文, 这点和 session cookies 的运行方式不同。
  • 打开多个相同的URL的Tabs页面,会创建各自的sessionStorage
  • 关闭对应浏览器窗口(Window)/ tab,会清除对应的sessionStorage

使用方法和属性与localStorage相同。

Cookies

HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的HTTP协议记录稳定的状态信息成为了可能。

Cookie本质是一个键值对。

Cookie的用途:

Cookie主要被用于以下三个方面:

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息);
  2. 个性化设置(如用户自定义设置、主题等);
  3. 浏览器行为跟踪(如跟踪分析用户行为等);

http创建Cookie:

服务端:在收到 HTTP 请求时,服务器可以在响应头里面添加若干个 Set-Cookie选项,每一个代表一个Cookie及其相应配置;
客户端:浏览器收到响应后通常会保存下 Cookie,之后对该服务器每一次请求中都通过Cookie请求头部将 Cookie 信息发送给服务器。

如:
服务端响应内容:

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

客户端之后的请求内容:

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

Cookie相关属性:

服务端会在响应的每一个Set-Cookie头部中为该Cookie添加可选属性,来指定它的过期时间、域、路径、有效期、适用站点等。

  • Expires=<date>: cookie 的最长有效时间,形式为符合 HTTP-date 规范的时间戳。如果没有设置这个属性,那么表示这是一个会话期 cookie 。一个会话结束于客户端被关闭时,这意味着会话期 cookie 在彼时会被移除。然而,很多Web浏览器支持会话恢复功能,这个功能可以使浏览器保留所有的tab标签,然后在重新打开浏览器的时候将其还原。与此同时,cookie 也会恢复,就跟从来没有关闭浏览器一样;

  • Max-Age=<non-zero-digit>: 在 cookie 失效之前需要经过的秒数。秒数为 0 或 -1 将会使 cookie 直接过期。一些老的浏览器(ie6、ie7 和 ie8)不支持这个属性。对于其他浏览器来说,假如二者 (指 Expires 和Max-Age) 均存在,那么 Max-Age 优先级更高;

  • Domain=<domain-value>: 指定 cookie 可以送达的主机名。假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。与之前的规范不同的是,域名之前的点号会被忽略。假如指定了域名,那么相当于各个子域名也包含在内了;

  • Path=<path-value>: 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。字符  %x2F ("/") 可以解释为文件目录分隔符,此目录的下级目录也满足匹配的条件(例如,如果 path=/docs,那么 "/docs", "/docs/Web/" 或者 "/docs/Web/HTTP" 都满足匹配的条件);

  • Secure: 一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器。然而,保密或敏感信息永远不要在 HTTP cookie 中存储或传输,因为整个机制从本质上来说都是不安全的,比如前述协议并不意味着所有的信息都是经过加密的。注意: 非安全站点(http:)已经不能再在 cookie 中设置 secure 指令了(在Chrome 52+ and Firefox 52+ 中新引入的限制);

  • HttpOnly: 设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由Document.cookie属性、XMLHttpRequestRequestAPIs 进行访问,可以用来防范跨站脚本攻击(XSS);

  • SameSite=Strict/Lax: 当为“Strict”时,完全禁止Cookie跨域发送;当为“Lax”时,除了以下三种情况外,Cookie不能跨域发送:

    • 链接:<a href="..."></a>
    • 预加载请求:<link rel="prerender" href="..."/>
    • GET表单:<form method="GET" action="...">

Cookie前缀:

  • __Secure- 前缀:以__Secure- 为前缀的 cookie(其中连接符是前缀的一部分),必须与 secure 属性一同设置,同时必须应用于安全页面(即使用 HTTPS 访问的页面)。
  • __Host- 前缀: 以__Host- 为前缀的 cookie,必须与 secure 属性一同设置,必须应用于安全页面(即使用 HTTPS 访问的页面),必须不能设置 domain 属性 (也就不会发送给子域),同时 path 属性的值必须为“/”。
// 当响应来自于一个安全域(HTTPS)的时候,二者都可以被客户端接受
Set-Cookie: __Secure-ID=123; Secure; Domain=example.com
Set-Cookie: __Host-ID=123; Secure; Path=/

// 缺少 Secure 指令,会被拒绝
Set-Cookie: __Secure-id=1

// 缺少 Path=/ 指令,会被拒绝
Set-Cookie: __Host-id=1; Secure

// 由于设置了 domain 属性,会被拒绝
Set-Cookie: __Host-id=1; Secure; Path=/; domain=example.com

Cookie类型:

  1. 会话期Cookie:
    会话期 cookies 将会在客户端关闭时被移除。 会话期 cookie 不设置 Expires 或 Max-Age 指令。注意浏览器通常支持会话恢复功能。

    Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/
    
  2. 持久化Cookie:
    持久化 Cookie 不会在客户端关闭时失效,而是在特定的日期(Expires)或者经过一段特定的时间之后(Max-Age)才会失效。

    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
    

js操作Cookie:

js只能操作没有设置httpOnly的Cookie。

  • 查看浏览器是否打开Cookie功能:

    // true表示开启Cookie功能
    window.navigator.cookieEnabled;
    
  • 查看Cookie:

    cosnt cookie = document.cookie;
    

    读取后的Cookie是一个字符串,该字符串包含所有的Cookie,
    每条Cookie以分号和空格(; )分隔(即, key=value键值对)。

  • 增加Cookie:

    document.cookie = newCookie;
    

    newCookie是一个键值对形式的字符串。需要注意的是,用这个方法一次只能对一个cookie进行设置或更新。

    • 以下可选的Cookie属性值可以跟在键值对后,用来具体化对Cookie的设定/更新,使用分号以作分隔:

      • ;path=path (例如 '/', '/mydir') 如果没有定义,默认为当前文档位置的路径;

      • ;domain=domain (例如 'example.com', 'subdomain.example.com') 如果没有定义,默认为当前文档位置的路径的域名部分。与早期规范相反的是,在域名前面加 . 符将会被忽视,因为浏览器也许会拒绝设置这样的cookie。如果指定了一个域,那么子域也包含在内;

      • ;max-age=max-age-in-seconds (例如一年为60*60*24*365);

      • ;expires=date-in-GMTString-format 如果没有定义,cookie会在对话结束时过期。这个值的格式参见Date.toUTCString()

      • ;secure (cookie只通过https协议传输);

    • cookie的值字符串可以用encodeURIComponent()来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值)。

  • 修改Cookie:

    document.cookie = nowCookie;
    

    给已存在的Cookie重新赋值,新值会覆盖旧值。
    注意:需要保证path和domain这两个值不变,否则会添加一个新的cookie。

  • 删除Cookie:
    将一个已经存在的cookie名字过期时间设置为过去的时间:

    document.cookie = 'uid=dkfywqkrhkwehf23;expires=' + new Date(0) + ';path=/;secure;'
    

例如:

document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"

Cookie的大小与数量限制:

不同的浏览器对Cookie的大小和数量的限制不一样,一般,单个域名下设置的Cookie不应超过30个,且每个Cookie的大小不应超过4kb,超过以后,Cookie将会被忽略,不会被设置。

Web SQL:

  • Web SQL Database API 并不是 HTML5 规范的一部分,但它是一个独立的规范,引入了一组使用SQL 操作客户端数据库的 APIs;
  • Web SQL Database 使用 SQL 来操纵客户端数据库的 API,这些 API 是异步的,规范中使用的是 SQLlite(SQL后端);
  • Web SQL Database 可以在最新版的 Safari、Chrome 和 Opera 浏览器中工作;
  • Web SQL Database 目前还没有删除数据库的 API,不支持删库;
  • 曾被 W3C 推荐标准,现已被废弃,应避免使用

核心方法:

  • openDatabase(dbName, version, desp, size, callback) - 打开或新建一个数据库;

    1. dbName:数据库名称
    2. version:版本号
    3. desp:描述文本
    4. size:数据库大小
    5. callback:创建回调,会在创建数据库后被调用。
  • transaction() - 读写数据库事务并提交或者回滚执行(rollback 函数);

  • executeSql() - 执行实际的 SQL 查询语句;

操作数据库:

// 创建数据库
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
// 对数据进行操作
db.transaction(function (tx) {
    // 创建TABLE
    tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)'); 
    // 使用动态值插入字段
    // 实例中的 e_id 和 e_log 是外部变量,executeSql 会映射数组参数中的每个条目给 "?"
    tx.executeSql('INSERT INTO LOGS (id,log) VALUES (?, ?)', [e_id, e_log]);
    // 查询数据库
    tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {
        console.log(results);
    }, null);
});

IndexedDB:

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。

IndexedDB为什么取代Web SQL:

Web SQL是基于SQLite的真正意义上的关系型数据库,(SQLite是遵守ACID的轻型的关系型数据库管理系统);而IndexedDB是NoSQL(非关系型数据库),使用键值对存储数据,且结构不固定,非常类似JavaScript中的纯对象。

“关系型数据库”对一致性要求非常严格,例如要写入100个数据,前99个成功了,结果第100个不合法,此时事务会回滚到最初状态。这样保证事务结束和开始时候的存储数据处于一致状态。非常适合银行这种对数据一致性要求非常高的场景。但是,这种一致性保证是牺牲了一部分性能的。

但是,对于微博,QQ空间这类web2.0应用,一致性却不是显得那么重要,比方说朋友A看我的主页和朋友B看我的主页信息有不一样,没什么大不了的,有个几秒差异很OK的。虽然这类应用对一致性要求不高,但对性能要求却很高,因为读写实在太频繁了。如果使用“关系型数据库”,一致性的好处没怎么收益,反而性能问题比较明显,此时,不保证一致性但性能更优的“非关系型数据库”则更合适。同时,由于“非关系型数据库”的数据结构不固定,非常容易扩展。由于对于社交网站,需求变动那是一日三餐常有的事,添加新字段在所难免,“非关系型数据库”轻松上阵,如果使用“关系型数据库”,多半要数据大变动,要好好琢磨琢磨了。

从气质上讲,“关系型数据库”稳重持久,“非关系型数据库”迅速灵动。

在前端领域,Web SQL Database是“关系型数据库”,indexedDB是“非关系型数据库”。

Web SQL直接写SQL语句,需要把JS对象转换成关系型的字符串语句;而indexedDB直接JS对象入库,通过调用API实现数据的增删改查。

indexed API简介:

浏览器对Storage对象的操作是同步的,不同于此,indexedDB是异步操作。
为了获取数据库的访问权限,需要在 window 对象的 indexedDB 属性上调用 open() 方法。该方法返回一个IDBRequest对象;异步操作通过在IDBRequest对象上触发事件来和调用程序进行通信。

  1. 打开数据库:
    window.indexedDB.open(dbName, version);
    
    • dbName:数据库名称,如果指定的数据库不存在,就会新建数据库;
    • version:数据库版本号,如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。当我们对数据库的字段进行增加或修改时候,需要增加版本。
    let db;
    const idbOpenRequest = indexedDB('testDB', 1);
    idbOpenRequest.onsuccess = function (event) {
      // 数据库信息
      const db = event.target.result;
      // 其他处理
    }
    idbOpenRequest.onerror = funtion(event) {
      // 异常处理
    }
    
  2. 创建主键和字段:
    // 该回调执行于:
    // 数据库首次创建版本,或者window.indexedDB.open传递的新版本(版本数值要比现在的高)
    idbOpenRequest.onupgradeneeded = function (event) {
      // 创建数据表
      const objStore = db.createObjectStore('testTable', {
        keyPath: 'id',
        autoIncrement: true,
      });
    
      // 新增字段
      objStore.createIndex('name', 'name', {
        unique: true,
      });
      objStore.createIndex('age', 'age');
    }
    
  3. 添加数据:
    由于数据库的操作都是基于事务(transaction)来进行,所以在增删改查之前都要先建立一个事务(transaction)。
    所有的操作都是异步,所以可以在成功或失败之后添加回调。
    // 创建事务
    const transcation = db.transcation(['testTable'], 'readwrite');
    // 找到对应数据表的对象
    const objStore = transcation.objectStore('testTable');
    // 添加数据
    const dbAddRequest = objStore.add({
      name: 'abc',
      age: 11,
    });
    dbAddRequest.onsuccess = function () {
      console.log('写入成功');
    };
    dbAddRequest.onerror = function () {
      console.log('写入失败');
    }
    
    • db.transcation(tableName, mode)
      • tableName: 数据表名称,可以是一个表名称的字符串,也可以是多个表名称的字符串列表;
      • mode:可以在事物中执行的访问类型,只有"readonly"和"readwrite";
  4. 查询数据:
    const dbGetRequest = objStore.get(1);
    dbGetRequest.onerror = function () {
      console.log('事务失败');
    };
    dbGetRequest.onsuccess = function (event) {
      const data = event.target.result;
      if (data) {
        console.log('Name: ' + data.name);
        console.log('Age: ' + data.age);
      } else {
        console.log('未获得数据记录');
      }
    };
    
  5. 删除数据:
    const dbDeleteRequest = objStore.delete(1);
    dbDeleteRequest.onsuccess = function () {
      console.log('删除成功');
    }
    dbDeleteRequest.onerror = function () {
      console.log('删除失败');
    }
    
  6. 遍历数据:
    const dbGetAllRequest = objStore.openCursor();
    dbGetAllRequest.onsuccess = function (event) {
      const cursor = event.target.result;
    
      if (cursor) {
        console.log('Id: ' + cursor.key);
        console.log('Name: ' + cursor.value.name);
        console.log('Age: ' + cursor.value.age);
        cursor.continue();
      } else {
        console.log('没有更多数据了!');
      }
    };
    
  7. 更新数据:
    const dbPutRequest = objStore.put({
      id: 1,
      name: 'aaa',
      age: 20,
    });
    dbPutRequest.onsuccess = function () {
      console.log('数据更新成功');
    };
    
    dbPutRequest.onerror = function () {
      console.log('数据更新失败');
    }
    

注意:正如大多数的 web 储存解决方案一样,IndexedDB 也遵守同源策略。因此当你在某个域名下操作储存数据的时候,你不能操作其他域名下的数据。

Trust Tokens:

Trust Tokkens是google推出的一套隐私友好型api,为了完全取代第三方cookie,新的Trust Token API将通过为广告客户无法跟踪的每个用户生成唯一的加密签名令牌来替代,但网站所有者仍可以访问以确定用户是机器人还是真实的人。

参考资料: