浏览器存储的不同类型 你知道几个?

存储 存储软件
在后端开发中,存储是工作的常见部分。应用程序数据存储在数据库中,文件存储在对象存储中,瞬态数据存储在高速缓存中……似乎存在无限种存储任何类型数据的可能性。

[[352564]]

在后端开发中,存储是工作的常见部分。应用程序数据存储在数据库中,文件存储在对象存储中,瞬态数据存储在高速缓存中……似乎存在无限种存储任何类型数据的可能性。但是,数据存储不仅限于后端,前端(浏览器)还具有许多存储数据的选项。我们可以通过利用这种存储方式来提升我们的应用性能,保存用户的偏好,在多个会话,甚至不同的计算机上保持应用状态。

在本文中,我们将通过不同的可能性在浏览器中存储数据。我们将涵盖每种方法的三个用例,以掌握其利弊。最后,你将能够决定什么存储是最适合你的用例。

让我们开始吧!

localStorage API

localStorage 是浏览器中最受欢迎的存储选项之一,也是许多开发人员的首选。数据跨会话存储,从不与服务器共享,并且可用于同一协议和域下的所有页面。存储空间限制为?5MB。

令人惊讶的是,谷歌Chrome团队并不建议使用这个选项,因为它屏蔽了主线程,而且web workers和service workers无法访问。他们推出了一个实验:KV Storage[1],作为一个更好的版本,但这只是一个试验,似乎还没有任何进展。

localStorage API 可作为 window.localStorage 使用,并且只能保存UTF-16字符串。在将数据保存到 localStorage 之前,我们必须确保将其转换为字符串。主要的三个功能是:

  1. setItem('key''value'
  2.  
  3. getItem('key'
  4.  
  5. removeItem('key'

它们都是同步的,因此使用起来很简单,但是它们会阻塞主线程。

值得一提的是,localStorage 有一个称为 sessionStorage 的双胞胎。唯一的区别是,存储在 sessionStorage 中的数据将仅持续当前会话,但API相同。

这个太简单了,相信大家都用过。

IndexedDB API

IndexedDB是浏览器中的现代存储解决方案。它可以存储大量的结构化数据,甚至文件和Blob。和每一个数据库一样,IndexedDB对数据进行索引,以便高效地运行查询。使用IndexedDB比较复杂,我们必须创建一个数据库,表,并使用事务。

与 localStorage 相比,IndexedDB需要更多代码。在例子中,我使用了原生API与Promise包装器,但我强烈建议使用第三方库来帮助你。我推荐的是localForage[2],因为它使用了同样的 localStorage API,但实现方式是逐步增强的,也就是说,如果你的浏览器支持IndexedDB,就会使用它;如果不支持,就会退回到 localStorage。

让我们来编写代码,前往我们的用户偏好示例吧!

  1. <input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'
  2. <label for="darkTheme">Dark theme</label><br> 
  1. let db; 
  2.  
  3. function toggle(on) { 
  4.   if (on) { 
  5.     document.documentElement.classList.add('dark');  
  6.   } else { 
  7.     document.documentElement.classList.remove('dark');     
  8.   } 
  9.  
  10. async function save(on) { 
  11.   const tx = db.transaction('preferences''readwrite'); 
  12.   const store = tx.objectStore('preferences'); 
  13.   store.put({key'darkTheme', value: on}); 
  14.   return tx.complete; 
  15.  
  16. async function load() { 
  17.   const tx = db.transaction('preferences''readonly'); 
  18.   const store = tx.objectStore('preferences'); 
  19.   const data = await store.get('darkTheme'); 
  20.   return data && data.value; 
  21.  
  22. async function onChange(checkbox) { 
  23.   const value = checkbox.checked; 
  24.   toggle(value); 
  25.   await save(value); 
  26.  
  27. function openDatabase() { 
  28.   return idb.openDB('my-db', 1, { 
  29.     upgrade(db) { 
  30.       db.createObjectStore('preferences', {keyPath: 'key'}); 
  31.     }, 
  32.   }); 
  33.  
  34. openDatabase() 
  35.   .then((_db) => { 
  36.     db = _db; 
  37.     return load(); 
  38.   }) 
  39.   .then((initialValue) => { 
  40.     toggle(initialValue); 
  41.     document.querySelector('#darkTheme').checked = initialValue; 
  42.   }); 

效果

idb 是我们使用的Promise包装器,而不是使用基于事件的低级API。首先要注意的是,对数据库的每次访问都是异步的,这意味着我们不会阻塞主线程,与 localStorage 相比,这是一个主要优势。

我们需要打开与数据库的连接,以便在整个应用程序中都可以使用它进行读写。我们给数据库起一个名字 my-db,一个模式版本 1,以及一个更新函数,以在版本之间应用更改,这与数据库迁移非常相似。我们的数据库架构很简单:只有一个object store preferences。object store 等效于SQL表,要写入或读取数据库,必须使用事务,这是使用IndexedDB的乏味部分。看一下演示中新的 save 和 load 功能。

毫无疑问,IndexedDB具有更多的开销,并且与 localStorage 相比,学习曲线更陡峭。对于键值的情况,使用 localStorage 或第三方库可能更有意义,它们将帮助我们提高效率。

  1. <div id="loading">loading...</div> 
  2. <ul id="list"
  3. </ul> 
  1. let db; 
  2.  
  3. async function loadPokemons() { 
  4.   const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=10'); 
  5.   const data = await res.json(); 
  6.   return data.results; 
  7.  
  8. function removeLoading() { 
  9.   const elem = document.querySelector('#loading'); 
  10.   if (elem) { 
  11.     elem.parentNode.removeChild(elem);  
  12.   } 
  13.  
  14. function appendPokemon(pokemon) { 
  15.   const node = document.createElement('li'); 
  16.   const textnode = document.createTextNode(pokemon.name); 
  17.   node.appendChild(textnode); 
  18.   document.querySelector('#list').appendChild(node); 
  19.  
  20. function clearList() { 
  21.   const list = document.querySelector('#list'); 
  22.   while (list.firstChild) { 
  23.     list.removeChild(list.lastChild); 
  24.   } 
  25.  
  26. function saveToCache(pokemons) { 
  27.   const tx = db.transaction('pokemons''readwrite'); 
  28.   const store = tx.objectStore('pokemons'); 
  29.   pokemons.forEach(pokemon => store.put(pokemon)); 
  30.   return tx.complete; 
  31.  
  32. function loadFromCache() { 
  33.   const tx = db.transaction('pokemons''readonly'); 
  34.   const store = tx.objectStore('pokemons'); 
  35.   return store.getAll(); 
  36.  
  37. function openDatabase() { 
  38.   return idb.openDB('my-db2', 1, { 
  39.     upgrade(db) { 
  40.       db.createObjectStore('pokemons', {keyPath: 'name'}); 
  41.     }, 
  42.   }); 
  43.  
  44. openDatabase() 
  45.   .then((_db) => { 
  46.     db = _db; 
  47.     return loadFromCache(); 
  48.   }) 
  49.   .then((cachedPokemons) => { 
  50.     if (cachedPokemons) { 
  51.       removeLoading(); 
  52.       cachedPokemons.forEach(appendPokemon); 
  53.       console.log('loaded from cache!'); 
  54.     } 
  55.     return loadPokemons(); 
  56.   }) 
  57.   .then((pokemons) => { 
  58.     removeLoading(); 
  59.     saveToCache(pokemons); 
  60.     clearList(); 
  61.     pokemons.forEach(appendPokemon); 
  62.     console.log('loaded from network!'); 
  63.   }); 

效果

你可以在此数据库中存储数百兆甚至更多。您可以将所有Pokémon存储在IndexedDB中,并使其脱机甚至建立索引!这绝对是用于存储应用程序数据的一种选择。

我跳过了第三个示例的实现,因为与 localStorage 相比,IndexedDB在这种情况下没有任何区别。即使使用 IndexedDB,用户仍然不会与他人分享所选页面,也不会将其作为书签供将来使用。它们都不适合这个用例。

Cookies

使用cookies是一种独特的存储方式,这是唯一的与服务器共享的存储方式。Cookies作为每次请求的一部分被发送。它可以是当用户浏览我们的应用程序中的页面或当用户发送Ajax请求时。这样我们就可以在客户端和服务器之间建立一个共享状态,也可以在不同子域的多个应用程序之间共享状态。本文中介绍的其他存储选项无法实现。需要注意的是:每个请求都会发送 cookie,这意味着我们必须保持 cookie 较小,以保持适当的请求大小。

Cookies的最常见用途是身份验证,这不在本文的讨论范围之内。就像 localStorage 一样,cookie只能存储字符串。这些cookie被连接成一个以分号分隔的字符串,并在请求的cookie头中发送。你可以为每个cookie设置很多属性,比如过期、允许的域名、允许的页面等等。

在例子中,我展示了如何通过客户端来操作cookie,但也可以在你的服务器端应用程序中改变它们。

  1. <input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'
  2. <label for="darkTheme">Dark theme</label> 
  1. function getCookie(cname) { 
  2.   const name = cname + '='
  3.   const decoded = decodeURIComponent(document.cookie); 
  4.   const split = decoded.split(';'); 
  5.   const relevantCookie = split.find((cookie) => cookie.indexOf(`${cname}=`) === 0); 
  6.   if (relevantCookie) { 
  7.     return relevantCookie.split('=')[1]; 
  8.   } 
  9.   return null
  10.  
  11. function toggle(on) { 
  12.   if (on) { 
  13.     document.documentElement.classList.add('dark');  
  14.   } else { 
  15.     document.documentElement.classList.remove('dark');     
  16.   } 
  17.  
  18. function save(on) { 
  19.   document.cookie = `dark_theme=${on.toString()}; max-age=31536000; SameSite=None; Secure`; 
  20.  
  21. function load() { 
  22.   return getCookie('dark_theme') === 'true'
  23.  
  24. function onChange(checkbox) { 
  25.   const value = checkbox.checked; 
  26.   toggle(value); 
  27.   save(value); 
  28.  
  29. const initialValue = load(); 
  30. toggle(initialValue); 
  31. document.querySelector('#darkTheme').checked = initialValue; 

效果还是跟前面一样

将用户的喜好保存在cookie中,如果服务器能够以某种方式利用它,就可以很好地满足用户的需求。例如,在主题用例中,服务器可以交付相关的CSS文件,并减少潜在的捆绑大小(在我们进行服务器端渲染的情况下)。另一个用例可能是在没有数据库的情况下,在多个子域应用之间共享这些偏好。

用JavaScript读写cookie并不像您想象的那么简单。要保存新的cookie,您需要设置 document.cookie ——在上面的示例中查看 save 函数。我设置了 dark_theme cookie,并给它添加了一个 max-age 属性,以确保它在关闭标签时不会过期。另外,我添加 SameSite 和 Secure 属性。这些都是必要的,因为CodePen使用iframe来运行这些例子,但在大多数情况下你并不需要它们。读取一个cookie需要解析cookie字符串。

Cookie字符串如下所示:

  1. key1=value1;key2=value2;key3=value3 

因此,首先,我们必须用分号分隔字符串。现在,我们有一个形式为 key1=value1 的Cookie数组,所以我们需要在数组中找到正确的元素。最后,我们将等号分开并获得新数组中的最后一个元素。有点繁琐,但一旦你实现了 getCookie 函数(或从我的例子中复制它:P),你就可以忘记它。

将应用程序数据保存在cookie中可能是个坏主意!它将大大增加请求的大小,并降低应用程序性能。此外,服务器无法从这些信息中获益,因为它是数据库中已有信息的陈旧版本。如果你使用cookies,请确保它们很小。

分页示例也不适合cookie,就像 localStorage 和 IndexedDB 一样。当前页面是我们想要与他人共享的临时状态,这些方法都无法实现它。

URL storage

URL本身并不是存储设备,但它是创建可共享状态的好方法。实际上,这意味着将查询参数添加到当前URL中,这些参数可用于重新创建当前状态。最好的例子是搜索查询和过滤器。如果我们在CSS-Tricks上搜索术语flexbox,则URL将更新为https://css-tricks.com/?s=flexbox。看看我们使用URL后,分享搜索查询有多简单?另一个好处是,你只需点击刷新按钮,就可以获得更新的查询结果,甚至可以将其收藏。

我们只能在URL中保存字符串,它的最大长度是有限的,所以我们没有那么多的空间。我们将不得不保持我们的状态小,没有人喜欢又长又吓人的网址。

同样,CodePen使用iframe运行示例,因此您看不到URL实际更改。不用担心,因为所有的碎片都在那里,所以你可以在任何你想要的地方使用它。

  1. <input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'
  2. <label for="darkTheme">Dark theme</label> 
  1. function toggle(on) { 
  2.   if (on) { 
  3.     document.documentElement.classList.add('dark');  
  4.   } else { 
  5.     document.documentElement.classList.remove('dark');     
  6.   } 
  7.  
  8. function save(on) { 
  9.   const params = new URLSearchParams(window.location.search); 
  10.   params.set('dark_theme'on.toString()); 
  11.   history.pushState(nullnull, `?${params.toString()}`); 
  12.  
  13. function load() { 
  14.   const params = new URLSearchParams(window.location.search); 
  15.   return params.get('dark_theme') === 'true'
  16.  
  17. function onChange(checkbox) { 
  18.   const value = checkbox.checked; 
  19.   toggle(value); 
  20.   save(value); 
  21.  
  22. const initialValue = load(); 
  23. toggle(initialValue); 
  24. document.querySelector('#darkTheme').checked = initialValue; 

效果还是一样

我们可以通过 window.location.search 访问查询字符串,幸运的是,可以使用 URLSearchParams 类对其进行解析,无需再应用任何复杂的字符串解析。当我们想读取当前值时,可以使用 get 函数,当我们想写时,可以使用 set。仅设置值是不够的,我们还需要更新URL。这可以使用 history.pushState 或 history.replaceState 来完成,取决于我们想要完成的行为。

我不建议将用户的偏好保存在URL中,因为我们必须将这个状态添加到用户访问的每一个URL中,而且我们无法保证;例如,如果用户点击了谷歌搜索的链接。

就像Cookie一样,由于空间太小,我们无法在URL中保存应用程序数据。而且即使我们真的设法存储它,网址也会很长,而且不吸引人点击。可能看起来像是钓鱼攻击的一种。

  1. <div>Select page:</div> 
  2. <div id="pages"
  3.   <button onclick="updatePage(0)">0</button> 
  4.   <button onclick="updatePage(1)">1</button> 
  5.   <button onclick="updatePage(3)">3</button> 
  6.   <button onclick="updatePage(4)">4</button> 
  7.   <button onclick="updatePage(5)">5</button> 
  8. </div> 
  9. <ul id="list"
  10. </ul> 
  1. async function loadPokemons(page) { 
  2.   const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10&offset=${page * 10}`); 
  3.   const data = await res.json(); 
  4.   return data.results; 
  5.  
  6. function appendPokemon(pokemon) { 
  7.   const node = document.createElement('li'); 
  8.   const textnode = document.createTextNode(pokemon.name); 
  9.   node.appendChild(textnode); 
  10.   document.querySelector('#list').appendChild(node); 
  11.  
  12. function clearList() { 
  13.   const list = document.querySelector('#list'); 
  14.   while (list.firstChild) { 
  15.     list.removeChild(list.lastChild); 
  16.   } 
  17.  
  18. function savePage(page) { 
  19.   const params = new URLSearchParams(window.location.search); 
  20.   params.set('page', page.toString()); 
  21.   history.pushState(nullnull, `?${params.toString()}`); 
  22.  
  23. function loadPage() { 
  24.   const params = new URLSearchParams(window.location.search); 
  25.   if (params.has('page')) { 
  26.     return parseInt(params.get('page')); 
  27.   } 
  28.   return 0; 
  29.  
  30. async function updatePage(page) { 
  31.   clearList(); 
  32.   savePage(page); 
  33.   const pokemons = await loadPokemons(page); 
  34.   pokemons.forEach(appendPokemon); 
  35.  
  36. const page = loadPage(); 
  37. updatePage(page); 

效果

就像我们的分页例子一样,临时应用状态是最适合URL查询字符串的。同样,你无法看到URL的变化,但每次点击一个页面时,URL都会以 ?page=x 查询参数更新。当网页加载时,它会查找这个查询参数,并相应地获取正确的页面。现在,我们可以把这个网址分享给我们的朋友,让他们可以享受我们最喜欢的神奇宝贝。

Cache API

Cache API是网络级的存储,它用于缓存网络请求及其响应。Cache API非常适合service worker,service worker可以拦截每一个网络请求,使用 Cache API 它可以轻松地缓存这两个请求。service worker也可以将现有的缓存项作为网络响应返回,而不是从服务器上获取。这样,您可以减少网络负载时间,并使你的应用程序即使处于脱机状态也能正常工作。最初,它是为service worker创建的,但在现代浏览器中,Cache API也可以在窗口、iframe和worker上下文中使用。这是一个非常强大的API,可以极大地改善应用的用户体验。

就像IndexedDB一样,Cache API的存储不受限制,您可以存储数百兆字节,如果需要甚至可以存储更多。API是异步的,所以它不会阻塞你的主线程,而且它可以通过全局属性 caches 来访问。

Browser extension

如果你建立一个浏览器扩展,你有另一个选择来存储你的数据,我在进行扩展程序daily.dev时发现了它。如果你使用Mozilla的polyfill,它可以通过 chrome.storage 或 browser.storage 获得。确保在你的清单中申请一个存储权限以获得访问权。

有两种类型的存储选项:local和sync。local存储是不言而喻的,它的意思是不共享,保存在本地。sync存储是作为谷歌账户的一部分同步的,你在任何地方用同一个账户安装扩展,这个存储都会被同步。两者都有相同的API,所以如果需要的话,来回切换超级容易。它是异步存储,因此不会像 localStorage 这样阻塞主线程。不幸的是,我不能为这个存储选项创建一个演示,因为它需要一个浏览器扩展,但它的使用非常简单,几乎和 localStorage 一样。有关确切实现的更多信息,请参阅Chrome文档。

结束

浏览器有许多选项可用于存储数据。根据Chrome团队的建议,我们的首选存储应该是IndexedDB,它是异步存储,有足够的空间来存储我们想要的任何东西。不鼓励使用 localStorage,但它比 IndexedDB 更易于使用。Cookies是与服务器共享客户端状态的一种好方法,但通常用于身份验证。

如果你想创建具有可共享状态的页面,如搜索页面,请使用URL的查询字符串来存储这些信息。最后,如果你建立一个扩展,一定要阅读关于 chrome.storage。

原文:https://css-tricks.com/a-primer-on-the-different-types-of-browser-storage/

作者:Ido Shamun

本文转载自微信公众号「 前端全栈开发者」,可以通过以下二维码关注。转载本文请联系 前端全栈开发者公众号。

 

责任编辑:武晓燕 来源: 前端全栈开发者
相关推荐

2022-12-26 23:38:10

浏览器扩展工具

2021-05-11 05:39:07

Edge微软浏览器

2013-08-27 12:42:42

浏览器

2011-04-12 11:46:26

Oracle优化器

2010-05-10 09:48:46

Oracle优化器

2024-01-18 00:16:07

2019-04-08 10:27:00

渲染浏览器DOM

2013-06-26 13:59:38

2024-01-26 11:08:57

C++函数返回不同类型

2021-04-13 05:36:18

C#null 可控

2011-03-30 08:27:48

C#

2010-12-16 10:54:07

SSL VPNVPN

2023-07-25 16:04:18

网络电缆光纤

2013-07-25 14:17:17

2010-09-15 15:39:03

CSS hack

2023-03-24 16:21:08

2019-11-12 08:53:32

PG数据数据库

2015-09-28 10:28:28

国外桌面浏览器盘点

2022-07-07 09:27:26

Syslinux加载程序

2022-09-21 09:03:46

机密计算数据安全
点赞
收藏

51CTO技术栈公众号