乐趣区

关于前端:浏览器存储的不同类型简介

在后端开发中,存储是工作的常见局部。应用程序数据存储在数据库中,文件存储在对象存储中,瞬态数据存储在高速缓存中……仿佛存在有限种存储任何类型数据的可能性。然而, 数据存储不仅限于后端 ,前端(浏览器)还具备许多存储数据的选项。咱们能够通过利用这种存储形式来晋升咱们的利用性能,保留用户的偏好,在多个会话,甚至不同的计算机上放弃利用状态。

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

让咱们开始吧!

localStorage API

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

令人诧异的是,谷歌 Chrome 团队并不倡议应用这个选项,因为它屏蔽了主线程,而且 web workers 和 service workers 无法访问。他们推出了一个试验:KV Storage,作为一个更好的版本,但这只是一个试验,仿佛还没有任何停顿。

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

  • setItem('key', 'value')
  • getItem('key')
  • removeItem('key')

它们都是同步的,因而应用起来很简略,然而它们会阻塞主线程。

值得一提的是,localStorage 有一个称为 sessionStorage 的双胞胎。惟一的区别是,存储在 sessionStorage 中的数据将仅继续以后会话,但 API 雷同。

这个太简略了,置信大家都用过。

IndexedDB API

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

localStorage 相比,IndexedDB 须要更多代码。在例子中,我应用了原生 API 与 Promise 包装器,但我强烈建议应用第三方库来帮忙你。我举荐的是 localForage,因为它应用了同样的 localStorage API,但实现形式是逐渐加强的,也就是说,如果你的浏览器反对 IndexedDB,就会应用它;如果不反对,就会退回到 localStorage

让咱们来编写代码,返回咱们的用户偏好示例吧!

<input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'>
<label for="darkTheme">Dark theme</label><br>
let db;

function toggle(on) {if (on) {document.documentElement.classList.add('dark'); 
  } else {document.documentElement.classList.remove('dark');    
  }
}

async function save(on) {const tx = db.transaction('preferences', 'readwrite');
  const store = tx.objectStore('preferences');
  store.put({key: 'darkTheme', value: on});
  return tx.complete;
}

async function load() {const tx = db.transaction('preferences', 'readonly');
  const store = tx.objectStore('preferences');
  const data = await store.get('darkTheme');
  return data && data.value;
}

async function onChange(checkbox) {
  const value = checkbox.checked;
  toggle(value);
  await save(value);
}

function openDatabase() {
  return idb.openDB('my-db', 1, {upgrade(db) {db.createObjectStore('preferences', {keyPath: 'key'});
    },
  });
}

openDatabase()
  .then((_db) => {
    db = _db;
    return load();})
  .then((initialValue) => {toggle(initialValue);
    document.querySelector('#darkTheme').checked = initialValue;
  });

成果

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

咱们须要关上与数据库的连贯,以便在整个应用程序中都能够应用它进行读写。咱们给数据库起一个名字 my-db,一个模式版本 1,以及一个更新函数,以在版本之间利用更改,这与数据库迁徙十分类似。咱们的数据库架构很简略:只有一个 object store preferences。object store 等效于 SQL 表,要写入或读取数据库,必须应用事务,这是应用 IndexedDB 的乏味局部。看一下演示中新的 saveload 性能。

毫无疑问,IndexedDB 具备更多的开销,并且与 localStorage 相比,学习曲线更平缓。对于键值的状况,应用 localStorage 或第三方库可能更有意义,它们将帮忙咱们提高效率。

<div id="loading">loading...</div>
<ul id="list">
</ul>
let db;

async function loadPokemons() {const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=10');
  const data = await res.json();
  return data.results;
}

function removeLoading() {const elem = document.querySelector('#loading');
  if (elem) {elem.parentNode.removeChild(elem); 
  }
}

function appendPokemon(pokemon) {const node = document.createElement('li');
  const textnode = document.createTextNode(pokemon.name);
  node.appendChild(textnode);
  document.querySelector('#list').appendChild(node);
}

function clearList() {const list = document.querySelector('#list');
  while (list.firstChild) {list.removeChild(list.lastChild);
  }
}

function saveToCache(pokemons) {const tx = db.transaction('pokemons', 'readwrite');
  const store = tx.objectStore('pokemons');
  pokemons.forEach(pokemon => store.put(pokemon));
  return tx.complete;
}

function loadFromCache() {const tx = db.transaction('pokemons', 'readonly');
  const store = tx.objectStore('pokemons');
  return store.getAll();}

function openDatabase() {
  return idb.openDB('my-db2', 1, {upgrade(db) {db.createObjectStore('pokemons', {keyPath: 'name'});
    },
  });
}

openDatabase()
  .then((_db) => {
    db = _db;
    return loadFromCache();})
  .then((cachedPokemons) => {if (cachedPokemons) {removeLoading();
      cachedPokemons.forEach(appendPokemon);
      console.log('loaded from cache!');
    }
    return loadPokemons();})
  .then((pokemons) => {removeLoading();
    saveToCache(pokemons);
    clearList();
    pokemons.forEach(appendPokemon);
    console.log('loaded from network!');
  });

成果

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

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

Cookies

应用 cookies 是一种独特的存储形式,这是惟一的与服务器共享的存储形式。Cookies 作为每次申请的一部分被发送。它能够是当用户浏览咱们的应用程序中的页面或当用户发送 Ajax 申请时。这样咱们就能够在客户端和服务器之间建设一个共享状态,也能够在不同子域的多个应用程序之间共享状态。本文中介绍的其余存储选项无奈实现。须要留神的是:每个申请都会发送 cookie,这意味着咱们必须放弃 cookie 较小,以放弃适当的申请大小。

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

在例子中,我展现了如何通过客户端来操作 cookie,但也能够在你的服务器端应用程序中扭转它们。

<input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'>
<label for="darkTheme">Dark theme</label>
function getCookie(cname) {
  const name = cname + '=';
  const decoded = decodeURIComponent(document.cookie);
  const split = decoded.split(';');
  const relevantCookie = split.find((cookie) => cookie.indexOf(`${cname}=`) === 0);
  if (relevantCookie) {return relevantCookie.split('=')[1];
  }
  return null;
}

function toggle(on) {if (on) {document.documentElement.classList.add('dark'); 
  } else {document.documentElement.classList.remove('dark');    
  }
}

function save(on) {document.cookie = `dark_theme=${on.toString()}; max-age=31536000; SameSite=None; Secure`;
}

function load() {return getCookie('dark_theme') === 'true';
}

function onChange(checkbox) {
  const value = checkbox.checked;
  toggle(value);
  save(value);
}

const initialValue = load();
toggle(initialValue);
document.querySelector('#darkTheme').checked = initialValue;

成果还是跟后面一样

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

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

Cookie 字符串如下所示:

key1=value1;key2=value2;key3=value3

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

将应用程序数据保留在 cookie 中可能是个坏主意!它将大大增加申请的大小,并升高应用程序性能。此外,服务器无奈从这些信息中获益,因为它是数据库中已有信息的古老版本。如果你应用 cookies,请确保它们很小。

分页示例也不适宜 cookie,就像 localStorageIndexedDB 一样。以后页面是咱们想要与别人共享的长期状态,这些办法都无奈实现它。

URL storage

URL 自身并不是存储设备,但它是创立可共享状态的好办法。实际上,这意味着将查问参数增加到以后 URL 中,这些参数可用于从新创立以后状态。最好的例子是搜寻查问和过滤器。如果咱们在 CSS-Tricks 上搜寻术语 flexbox,则 URL 将更新为 https://css-tricks.com/?s=fle…。看看咱们应用 URL 后,分享搜寻查问有多简略?另一个益处是,你只需点击刷新按钮,就能够取得更新的查问后果,甚至能够将其珍藏。

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

同样,CodePen 应用 iframe 运行示例,因而您看不到 URL 理论更改。不必放心,因为所有的碎片都在那里,所以你能够在任何你想要的中央应用它。

<input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'>
<label for="darkTheme">Dark theme</label>
function toggle(on) {if (on) {document.documentElement.classList.add('dark'); 
  } else {document.documentElement.classList.remove('dark');    
  }
}

function save(on) {const params = new URLSearchParams(window.location.search);
  params.set('dark_theme', on.toString());
  history.pushState(null, null, `?${params.toString()}`);
}

function load() {const params = new URLSearchParams(window.location.search);
  return params.get('dark_theme') === 'true';
}

function onChange(checkbox) {
  const value = checkbox.checked;
  toggle(value);
  save(value);
}

const initialValue = load();
toggle(initialValue);
document.querySelector('#darkTheme').checked = initialValue;

成果还是一样

咱们能够通过 window.location.search 拜访查问字符串,侥幸的是,能够应用 URLSearchParams 类对其进行解析,无需再利用任何简单的字符串解析。当咱们想读取以后值时,能够应用 get 函数,当咱们想写时,能够应用 set。仅设置值是不够的,咱们还须要更新 URL。这能够应用 history.pushStatehistory.replaceState 来实现,取决于咱们想要实现的行为。

我不倡议将用户的偏好保留在 URL 中,因为咱们必须将这个状态增加到用户拜访的每一个 URL 中,而且咱们无奈保障;例如,如果用户点击了谷歌搜寻的链接。

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

<div>Select page:</div>
<div id="pages">
  <button onclick="updatePage(0)">0</button>
  <button onclick="updatePage(1)">1</button>
  <button onclick="updatePage(3)">3</button>
  <button onclick="updatePage(4)">4</button>
  <button onclick="updatePage(5)">5</button>
</div>
<ul id="list">
</ul>
async function loadPokemons(page) {const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10&offset=${page * 10}`);
  const data = await res.json();
  return data.results;
}

function appendPokemon(pokemon) {const node = document.createElement('li');
  const textnode = document.createTextNode(pokemon.name);
  node.appendChild(textnode);
  document.querySelector('#list').appendChild(node);
}

function clearList() {const list = document.querySelector('#list');
  while (list.firstChild) {list.removeChild(list.lastChild);
  }
}

function savePage(page) {const params = new URLSearchParams(window.location.search);
  params.set('page', page.toString());
  history.pushState(null, null, `?${params.toString()}`);
}

function loadPage() {const params = new URLSearchParams(window.location.search);
  if (params.has('page')) {return parseInt(params.get('page'));
  }
  return 0;
}

async function updatePage(page) {clearList();
  savePage(page);
  const pokemons = await loadPokemons(page);
  pokemons.forEach(appendPokemon);
}

const page = loadPage();
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.storagebrowser.storage 取得。确保在你的清单中申请一个存储权限以取得拜访权。

有两种类型的存储选项:local 和 sync。local 存储是显而易见的,它的意思是不共享,保留在本地。sync 存储是作为谷歌账户的一部分同步的,你在任何中央用同一个账户装置扩大,这个存储都会被同步。两者都有雷同的 API,所以如果需要的话,来回切换超级容易。它是异步存储,因而不会像 localStorage 这样阻塞主线程。可怜的是,我不能为这个存储选项创立一个演示,因为它须要一个浏览器扩大,但它的应用非常简单,简直和 localStorage 一样。无关确切实现的更多信息,请参阅 Chrome 文档。

完结

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

如果你想创立具备可共享状态的页面,如搜寻页面,请应用 URL 的查问字符串来存储这些信息。最初,如果你建设一个扩大,肯定要浏览对于 chrome.storage


微信搜寻【前端全栈开发者】关注这个脱发、摆摊、卖货、继续学习的程序员,第一工夫浏览最新文章,会优先两天发表新文章。关注即可大礼包,准能为你节俭不少钱!

退出移动版