Different types of browser storage

Time:2021-2-15

Different types of browser storage

In back-end development, storage is a common part of the work. Application data is stored in the database, files in the object store, transient data in the cache There seems to be an infinite number of possibilities for storing any type of data. But,Data storage is not limited to the back endThe front end (browser) also has many options for storing data. We can use this storage method to improve our application performance, save user preferences, and keep the application state in multiple sessions or even different computers.

In this article, we will store data in browsers through different possibilities. We will cover three use cases of each method to grasp its advantages and disadvantages. Finally, you will be able to decide what storage is the best use case for you.

Let’s get started!

localStorage API

localStorageIt is one of the most popular storage options in browsers, and it is also the first choice for many developers. Data is stored across sessions, never shared with the server, and can be used for all pages under the same protocol and domain. The storage space is limited to 5MB.

Surprisingly, the Google Chrome team doesn’t recommend this option because it blocks the main thread and web workers and service workers can’t access it. They launched an experiment:KV StorageAs a better version, but it’s just an experiment and it doesn’t seem to have made any progress.

localStorage APICan be used aswindow.localStorageAnd can only save utf-16 strings. Save data tolocalStorageBefore that, we have to make sure we convert it to a string. The main three functions are as follows:

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

They are synchronous, so they are easy to use, but they block the main thread.

It is worth mentioning that,localStorageThere’s one calledsessionStorageMy twins. The only difference is that it’s stored insessionStorageThe data in will only last for the current session, but the API is the same.

This is too simple. I believe everyone has used it.

IndexedDB API

Indexeddb is a modern storage solution in browser. It can store large amounts of structured data, even files and blobs. Like every database, indexeddb indexes data to run queries efficiently. Using indexeddb is more complicated. We have to create a database, tables and use transactions.

AndlocalStorageIndexeddb requires more code than indexeddb. In the example, I used the native API and promise wrapper, but I strongly recommend using a third-party library to help you. What I recommend islocalForageBecause it uses the samelocalStorageAPI, but the implementation is gradually enhanced, that is, if your browser supports indexeddb, it will be used; if not, it will be returned to thelocalStorage

Let’s write the code and go to our user preferences example!

<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;
  });

effect

Different types of browser storage

idbIt’s the promise wrapper we use, not the low-level event based API. The first thing to note is that every access to the database is asynchronous, which means that we don’t block the main thread and communicate with each otherlocalStorageIn contrast, this is a major advantage.

We need to open the connection to the database so that it can be used for reading and writing throughout the application. We give the database a namemy-db, a schema version1, and an update function to apply changes between versions, which is very similar to database migration. Our database architecture is simple: there is only one object storepreferences. Object store is equivalent to SQL table. To write to or read from database, transaction must be used, which is the tedious part of using indexeddb. Take a look at the new ones in the demosaveandloadFunction.

There’s no doubt that indexeddb has more overhead, and it’s similar tolocalStorageIn contrast, the learning curve is steeper. For the case of key value, uselocalStorageOr third-party libraries may be more meaningful, they will help us improve efficiency.

<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!');
  });

effect

Different types of browser storage

You can store hundreds of megabytes or more in this database. You can store all Pok é mon in indexeddb and take it offline or even index it! This is definitely an option for storing application data.

I’ve skipped the implementation of the third example because it’s similar to thelocalStorageCompared to indexeddb, there is no difference in this case. Even with indexeddb, users will not share the selected page with others or bookmark it for future use. None of them is suitable for this use case.

Cookies

Using cookies is a unique way of storage, which is the only way to share with the server. Cookies are sent as part of each request. It can be when the user browses a page in our application or when the user sends an Ajax request. In this way, we can establish a shared state between the client and the server, and we can also share the state among multiple applications in different subdomains. The other storage options described in this article cannot be implemented. Note that every request is sentcookieThis means that we have to keep the cookie small to keep the request size appropriate.

The most common use of cookies is authentication, which is beyond the scope of this article. It’s likelocalStorageThe same,cookieOnly strings can be stored. These cookies are concatenated into a semicolon separated string and sent in the cookie header of the request. You can set many properties for each cookie, such as expiration, allowed domain name, allowed page, etc.

In the example, I showed you how to manipulate cookies through the client, but you can also change them in your server-side application.

<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;

The effect is the same as before

Different types of browser storage

Users’ preferences are stored in cookies. If the server can make use of it in some way, it can well meet the needs of users. For example, in the theme case, the server can deliver the relevant CSS files and reduce the potential bundle size (in the case of server-side rendering). Another use case may be to share these preferences among multiple sub domain applications without a database.

Reading and writing cookies in JavaScript is not as easy as you think. To save a new cookie, you need to set thedocument.cookie——See in the example abovesaveFunction. I set it updark_themeCookie and add amax-ageProperty to ensure that it does not expire when the label is closed. In addition, I addSameSiteandSecureProperty. These are necessary because codepen uses iframe to run these examples, but in most cases you don’t need them. Reading a cookie requires parsing the cookie string.

The cookie string is as follows:

key1=value1;key2=value2;key3=value3

So first, we have to separate the strings with semicolons. Now, we have a form calledkey1=value1So we need to find the correct element in the array. Finally, we separate the equal sign and get the last element in the new array. It’s a bit cumbersome, but once you do itgetCookieFunction (or copy it from my example: P), you can forget it.

It can be a bad idea to save application data in a cookie! It will greatly increase the size of the request and reduce application performance. In addition, the server cannot benefit from this information because it is an obsolete version of the information already in the database. If you use cookies, make sure they are small.

The paging example is also not suitable for cookies, likelocalStorageandIndexedDBIt’s the same. The current page is a temporary state that we want to share with others, and none of these methods can achieve it.

URL storage

URL itself is not a storage device, but it is a good way to create sharable state. In effect, this means adding query parameters to the current URL, which can be used to recreate the current state. The best examples are search queries and filters. If we search for the term flexbox on CSS tricks, the URL will be updated tohttps://css-tricks.com/?s=fle…. Let’s see how easy it is to share search queries after we use URLs? Another advantage is that you just need to click the refresh button to get updated query results, or even collect them.

We can only save the string in the URL, its maximum length is limited, so we don’t have that much space. We’re going to have to keep our status small and no one likes long and scary URLs.

Again, codepen runs the sample using iframe, so you don’t see the actual URL change. Don’t worry, because all the pieces are there, so you can use it wherever you want.

<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;

The effect is the same

Different types of browser storage

We can go through itwindow.location.searchTo access the query string, fortunately, you can use theURLSearchParamsClass to parse it without any complex string parsing. When we want to read the current value, we can usegetFunction, when we want to write, we can useset. It’s not enough to just set the value, we also need to update the URL. This can be usedhistory.pushStateorhistory.replaceStateIt depends on what we want to do.

I don’t recommend saving user preferences in URLs, because we have to add this state to every URL that the user visits, and we can’t guarantee it; for example, if the user clicks on a Google search link.

Just like cookies, we can’t save the application data in the URL because the space is too small. And even if we do manage to store it, the URL will be long and unattractive. It might look like a kind of fishing attack.

<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);

effect

Different types of browser storage

Just like our pagination example, temporary application state is the most suitable for URL query string. Again, you can’t see the changes in the URL, but every time you click on a page, the URL will change to?page=xQuery parameter update. When a web page is loaded, it looks for the query parameter and gets the correct page accordingly. Now, we can share this website with our friends, so that they can enjoy our favorite magic baby.

Cache API

Cache API is network level storage, which is used to cache network requests and their responses. Cache API is very suitable for service worker. Service worker can intercept every network request. Using cache API, it can easily cache these two requests. Service worker can also return an existing cache entry as a network response instead of getting it from the server. In this way, you can reduce network load time and make your application work even when it is offline. Initially, it was created for service worker, but in modern browsers, the cache API can also be used in windows, iframes, and worker contexts. This is a very powerful API that can greatly improve the user experience of the application.

Just like indexeddb, the storage of cache API is unlimited. You can store hundreds of megabytes, or even more if you need to. The API is asynchronous, so it won’t block your main thread, and it can use global propertiescachesCome and visit.

Browser extension

If you build a browser extension, you have another option to store your data. I’m working on the extension daily.dev I found it when I saw it. If you use Mozilla’s Polyfill, it canchrome.storageorbrowser.storageget. Make sure you apply for a storage right in your list to get access.

There are two types of storage options: local and sync. Local storage is self-evident, it means not to share, save locally. Sync storage is synchronized as part of a Google account. If you install extensions with the same account anywhere, the storage will be synchronized. Both have the same API, so it’s super easy to switch back and forth if you need to. It’s asynchronous storage, so it’s not likelocalStorageThis blocks the main thread. Unfortunately, I can’t create a demo for this storage option because it requires a browser extension, but it’s very simple to use, almost as simple aslocalStorageIt’s the same. For more information about the exact implementation, see the chrome documentation.

end

Browsers have many options for storing data. According to the chrome team’s suggestion, our preferred storage should be indexeddb, which is asynchronous storage and has enough space to store anything we want. Use is not encouragedlocalStorageBut it’s better thanIndexedDBIt’s easier to use. Cookies are a good way to share client state with the server, but are usually used for authentication.

If you want to create a sharable page, such as a search page, use the query string of the URL to store the information. Finally, if you build an extension, be sure to read about itchrome.storage


Wechat search [front end full stack developer] focuses on hair loss, stallsSelling goodsContinuous learning programmers, the first time to read the latest articles, will give priority to two days to publish new articles. Attention can be a big gift package, can save you a lot of money!

Different types of browser storage