For a website, important data is usually stored in a database on the server. But sometimes we cache some data in the browser for convenience. Such as session state management (including user authentication tokens, account information, shopping carts, etc.), personalization (including subject configuration, language configuration, etc.), and user behavior tracking, etc. This reduces unnecessary requests, improves front-end application performance, and reduces server stress. There are a lot of browser storage technologies, Cookie, Web Storage (Local Storage, Session Storage), IndexedDB, Web SQL, Trust Tokens, Cache Storage and Application Cache. This article focuses on the most commonly used Cookie, WebStorage and IndexedDB three storage technologies.

Cookie

Cookies used to be the most popular client-side storage technology. Since HTTP is stateless, we have no way of knowing whether two requests are from the same user and what the current state of that user is. For example, if you click the purchase button and send an HTTP request, the server may need to know which user you are, how much balance you have under your account, how many coupons, which bank cards are bound, whether the delivery address is perfect, etc. So the fundamental purpose of cookies is to help HTTP maintain state. For this kind of scenario, the browser introduces the cookie scheme to record this information. The specific specification is in RFC6265. However, with the development of browser Storage technology, more developers are choosing to use Local Storage in conjunction with JWT for user state Storage, which seems to be a better approach. But it’s still important to know about cookies, because there are still a lot of projects that are developed using cookies. Cookie features:

  • The vast majority of storage apis belong to the BOM; only cookies belong to the DOM.

Disadvantages of cookies:

  • There is a size limit, usually 4 K. However, this is a limit on the value of a single cookie, not all cookies.
  • Too large, not good for performance. Because cookies are automatically attached to all requests under the same domain name, many static resources, such as JS, CSS, PNG and other files will also be accompanied by cookies.
  • It is automatically sent to the server and cannot be cancelled manually.
  • The API design is inelegant and difficult to operate.

The use of cookies

Set the Cookie

The cookie can be passed in both directions. The cookie can be set by the server and passed to the browser. It can also be set by the browser and passed to the server.

The server sets Cookie set-cookie

Cookie_name =cookie_value; Set cookie =cookie; Set cookie =cookie_value; Space. See the following example of using Express. server.js

var express = require("express");
const path = require("path");
var app = express();

app.get("/".function (req, res) {
  res.setHeader('Set-Cookie'['userID=123456'.'lang=zh-CN'])
  res.sendFile(path.resolve(__dirname, './index.html'))
})

app.listen(3000);
Copy the code

The cookie fields userID and lang are set in the server’s request header. index.js

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Cookie</title>
  </head>
  <body>
    <div id="info"></div>
    <script>
      const infoEl = document.getElementById('info')
      infoEl.innerHTML = document.cookie;
    </script>
  </body>
</html>
Copy the code

The browser automatically sets the set-cookie in the HTTP response header to the document. Cookie, which is the default behavior of the browser.

Browser Setting Cookie

Setting cookies using JS is as simple as setting document.cookie = xx. Add an interface.

app.get("/cookie".function (req, res) {
  console.log(req.headers.cookie);
  res.send("")})Copy the code

Add a button.

<button id="btn">Send the request</button>
Copy the code

Add code to set the cookie and request the interface.

const btnEl = document.getElementById('btn')
btnEl.addEventListener('click'.() = >{
  document.cookie = "userID=555555"
  fetch('http://localhost:3000/cookie')});Copy the code

This allows you to change the value of the cookie and pass it to the server. Any cookie in the browser is appended to the cookie field in the request header by default. This is also the default behavior of the browser.

The life cycle

All data stored in the browser, essentially cached data, is deleted or updated by some mechanism. Cookies can be broadly divided into two categories, session-lasting cookies and persistent cookies. Session cookies are automatically deleted after the browser closes. Persistent cookies need to be set to either an expiration time (Expires) or an expiration time (max-age). This life cycle only works for the browser, and both expiration time and expiration date are calculated based on the browser’s time. In most cases, the backend framework encapsulates some convenient API, such as setting expiration time in Express and the expiration date can be written like this.

app.get("/".function (req, res) {
  res.cookie("userID"."123456", {
    maxAge: 2 * 1000.expires: Date.now() + 1000}); res.sendFile(path.resolve(__dirname,"./index.html"));
});
Copy the code

No matter what API is used to complete the cookie setting, the final effect is to add a set-cookie field in the HTTP Response headers, as follows:



This indicates that the validity period of the cookie field userID is 2 seconds, and the expiration time is the current time + 1 second.

When both fields are set, the relatively recent time takes effect.

Finally, you can add this script to your index. HTML to see what happens after it expires.

console.log(document.cookie);
setTimeout(() = > {
  console.log(document.cookie);
}, 1100);
Copy the code

scope

domain

By default, cookies can only be used under origin and do not contain subdomains. For example, www.a.com is available by default, but b.c.com is not. To share cookies with subdomains, you need to set the Domain properties. For example, domaina.com, so that *.a.com can share cookies.

path

Similarly, we can specify which paths can accept cookies. The control path attribute is PATH, such as path=/docs.

sameSite

The sameSite property allows the server to request that a cookie field not be sent on a cross-site request, thus preventing CSRF attacks.

  • None: The browser continues to send cookies on the same site request and cross-site request, and the cookies are case insensitive.
  • Strict: The browser sends cookies only when visiting the same site. (An enhancement of the restrictions on existing Cookies, as described above under “Cookie scope”)
  • Lax: Similar to Strict except when a user navigates from an external site to a URL (for example, via a link). In the new version of the browser, as the default option, same-site cookies will be reserved for some cross-site sub-requests, such as image loading or frames calls, but will only be sent when the user navigates to the URL from the external site. For example, link link.

security

secure

When secure is set, the cookie field cannot be applied to HTTP requests, only TO HTTPS requests.

http-only

After http-only is marked, the cookie field cannot be accessed by document.cookie. Some important information should use this tag. HTTP only can mitigate XSS attacks.

Delete the cookie

JavaScript cannot delete cookies directly. You must use a strange way to delete cookies, which is to adjust the expiration time of cookies to a much earlier time. For example, to delete foo, write something like this.

document.cookie = "foo=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
Copy the code

js cookie

Since cookies are not operated in a friendly way in the browser, you can use some libraries that encapsulate cookie operations and provide a more elegant API, such as JS-cookie. After using jS-cookie library, the method of cookie operation is as follows:

Cookies.set('foo'.'bar')/ / set
Cookies.get('foo')/ / to get
Cookies.remove('foo')/ / remove
Copy the code

cookieStorage

Since Chrome version 87, the cookieStorage API has been supported. This API allows for better manipulation of cookies.

cookieStorage.set("foo"."bar")
cookieStorage.get("foo")
cookieStorage.delete("foo")
Copy the code

In addition to the above three apis, cookieStorage provides the Change event, which you can listen for in response to cookie changes.

The Session Cookie model

Session cookies are a classic model for storing user state. The process is that the server creates a session for each user, which is usually stored in the server’s memory. Each user gets a session ID after authentication, which is stored in a cookie. Then every time the request comes from the user, the cookie will be retrieved from the request header, and the session will be queried, and if the session exists, the data will be retrieved for business logic processing. Here is an example of a session implemented using Express.

var express = require("express");
var cookies = require("cookie-parser");

var app = express();
app.use(cookies());

const session = new Map(a);function genid() {
  return Math.random();
}

app.get("/".function (req, res, next) {
  if (req.cookies && "sid" in req.cookies && session.has(+req.cookies.sid)) {
    res.setHeader("Content-Type"."text/html; charset=utf-8");
    res.write("

You are logged in

"
); res.end(); } else { var sid = genid(); res.cookie("sid", sid); session.set(sid, {}); res.end("welcome to the session demo. refresh!"); }}); app.listen(3011); Copy the code

To implement session, you need to generate session ID and maintain session by yourself, which is troublesome. Usually we use libraries that can help us do this, such as Express-Session.

var express = require("express");
const session = require("express-session");
var app = express();

app.use(
  session({
    secret: "keyboard cat".cookie: { maxAge: 60000}})); app.get("/".function (req, res, next) {
  if (req.session.views) {
    req.session.views++;
    res.setHeader("Content-Type"."text/html");
    res.write("<p>views: " + req.session.views + "</p>");
    res.write("<p>expires in: " + req.session.cookie.maxAge / 1000 + "s</p>");
    res.end();
  } else {
    req.session.views = 1;
    res.end("welcome to the session demo. refresh!"); }}); app.listen(3011);
Copy the code

The advantages and disadvantages

The following is an analysis of the advantages and disadvantages of cookie Session model.

  • Browser Default Technologies
  • Simple key-value pair storage structure
  • Automatic clearing after expiration is supported
  • Automatic carry by default

Disadvantages:

  • Cannot cross domain names
  • Can’t cross browsers
  • Single sign-on is difficult to implement

The URL rewrite

If cookies are disabled by some users or some browsers, they can be passed through URL rewriting.

Take the following request.



After cookies are disabled, change tohttp://localhost:3011/?sid=0.9382480676608358

WebStorage

Web Storage is a new storage scheme launched in HTML5, is an improvement on the cookie. Web storage is classified into two types. One is session level sessionStorage, which is automatically cleared after the browser is closed and used for temporary storage. The other is localStorage, the data persistence storage to the hardware device, even if the browser is closed will not be cleared, for permanent storage. The difference between the two is only the length of time the data is stored, nothing else. WebStorage makes the following improvements compared to cookies:

  • The maximum capacity is 10 MB. Different browsers have different sizes, ranging from 2.5MB to 10MB.
  • It is not automatically added to the HTTP request headers, regardless of communication.
  • A more friendly API.

Although WebStorage has made many improvements, it does not have protection policies such as HTTP only if it is attacked by XSS, so it is more vulnerable to data theft. In terms of security, WebStorage is no better than cookies.

Basic usage

Session Storage API is very simple, only setItem, getItem, removeItem, key and clear five methods, the operation is as follows:

sessionStorage.setItem('key'.'value')// Set a key-value pair with the first parameter being the key and the second parameter being the value
sessionStorage.getItem('key')// Get the value named key
sessionStorage.removeItem('key')// Remove the value named key
sessionStorage.key(0)// Get the NTH key name in the storage
sessionStorage.clear()// Clean all
Copy the code

The apis are exactly the same. Therefore, the API of local Storage is as follows:

localStorage.setItem('key'.'value')// Set a key-value pair with the first parameter being the key and the second parameter being the value
localStorage.getItem('key')// Get the value named key
localStorage.removeItem('key')// Remove the value named key
localStorage.key(0)// Get the NTH key name in the storage
localStorage.clear()// Clean all
Copy the code

In addition to the API operations described above, you can also manipulate them as objects.

sessionStorage.key = 'value'// Set a key-value pair with the first parameter being the key and the second parameter being the value
sessionStorage.key// Get the value named key
delete sessionStorage.key// Remove the value named key
Copy the code

Listen for events across pages

In some complex scenarios, we may need to access storage changes in both pages to handle some things, so we need to listen for storage changes. The WebStorage provides storage events. Listen for events in page1.html.

<! DOCTYPEhtml>
<html>
  <body>
    <script>
      window.addEventListener("storage".(e) = > {
        console.log(e);
      });
    </script>
  </body>
</html>
Copy the code

Store the data in Page2.html.

<! DOCTYPEhtml>
<html>
  <body>
    <input type="text" />
    <button onclick="save()">storage</button>
    <script>
      function save() {
        localStorage.setItem(
          "value".document.getElementsByTagName("input") [0].value
        );
      }
    </script>
  </body>
</html>
Copy the code

The event of storage contains a lot of useful data.

  • Key: indicates the changed key.
  • OldValue: indicates the oldValue before modification.
  • NewValue: indicates the newValue after modification.
  • Target: Window object that represents the document in which the data is being modified.
  • Url: Indicates the URL of the document where the data is modified.
  • Type: indicates the storage type.
  • StorageArea: indicates a storage object, pointing to either sessionStorage or localStorage.

Note: The storage event cannot respond to the current page operation.

WebStorage and JWT implement user authentication

The first problem many people face when switching from cookies to WebStorage is probably user authentication. Another popular technology for storing user information is JWT. JWT can replace Session.

JWT

JWT stands for JSON Web Token and the official specification is in RFC7519. JWT is an open standard for sharing security information between two parties (client and server). Each JWT is two encoded JSON objects. JWT can choose different encryption algorithms to encrypt the JSON object to ensure that the generated JWT string cannot be tampered with arbitrarily. For example, here’s the JSON:

{
  "user_id": "1234567890"."user_name": "Li Ming"
}
Copy the code

Encryption is performed using the HS256 encryption algorithm.

{
  "alg": "HS256"."typ": "JWT"
}
Copy the code

The result is a string of text.

Bmwjsnfg0g1uh5qjvnxbz eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. EyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsInVzZXJfbmFtZSI6IuadjuaYjiJ9. 4 CfFjOL8E4uDZ5Khnr00iICopy the code

The JWT string consists of three parts, each separated by a. It looks like xxXX.yyyyy.zzzzz. They parse three JSON objects, the Header, the Payload, and the Verify Signature. The header mainly contains the signature algorithm type, such as:

{
  "alg": "HS256"."typ": "JWT"
}
Copy the code

The content of the payload is defined by you. But it usually doesn’t contain much information to keep the JWT compact. The final signature can specify its own key (secret).

The advantage of JWT

With the cookie session model, user authentication information is stored by the server. Using JWT, the user’s authentication information is stored by the client, which saves the storage space of the server. More importantly, it is a stateless authentication mode, which makes it easier to achieve single sign-on. The only drawback to a session is that it has to be resolved on the server every time. But it’s also a general trade-off of performance for space.

encryption

When we get the JWT token and need to store it somewhere, we usually choose to use LocalStorage or SessionStorage depending on the memory mode the user chooses. For example, if the user selects remember password, we will store JWT in LocaStorage, otherwise we will store it in SessionStorage. Although JWT officially does not recommend that we save sensitive information in WebStorage, we save it in cookies, because cookies are protected by HTTP only. However, considering the convenience of the WebStorage API, many people prefer to store their data in WebStorage. To protect the data, we should always verify and encrypt the data in WebStorage. In this way, security can be improved to a certain extent, and the consequences of being attacked can be reduced. There are libraries that can do this, such as Secure-LS. However, if possible, I personally recommend putting JWT in a Cookie and setting HTTP only.

Authorization

The HTTP request header has an Authorization field that is used to send user credentials. This is where the JWT that we generate usually goes. Its format is as follows:

Authorization: <type> <credentials>
Copy the code

Type can be set to a variety of values. Such as the validation schemes in Basic, IANA, or Amazon. Set JWT in Authorization as follows:

Authorization: Bearer Bmwjsnfg0g1uh5qjvnxbz eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. EyJ1c2VyX2lkIjoiMTIzNDU2Nzg5MCIsInVzZXJfbmFtZSI6IuadjuaYjiJ9. 4 CfFjOL8E4uDZ5Khnr00iICopy the code

Demo

Many programming languages provide JWT libraries, such as JsonWebToken in Nodejs. Here is an example of a JWT program. server.js

var express = require("express");
var bodyParser = require("body-parser");
const path = require("path");
var jwt = require("jsonwebtoken");
var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.get("/".function (req, res, next) {
  res.sendFile(path.resolve(__dirname, "./index.html"));
});

app.post("/login".function (req, res) {
  var token = jwt.sign({ username: req.body.username }, "shhhhh");
  res.send({ token });
});

app.listen(3011);
Copy the code

index.html

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
  </head>
  <body>
    <input id="username" type="text" />
    <input id="password" type="password" />
    <input id="btn" value="Login" type="submit" />
    <script>
      document.getElementById("btn").addEventListener("click".() = > {
        const username = document.getElementById("username").value;
        const password = document.getElementById("password").value;

        fetch("./login", {
          method: "post".body: JSON.stringify({ username, password }),
          headers: { "Content-Type": "application/json" },
        })
          .then((res) = > res.json())
          .then((data) = > {
            document.writeln(JSON.stringify(data));
          });
      });
    </script>
  </body>
</html>
Copy the code

IndexedDB

IndexedDB is a lower-level API for storing large amounts of data. WebStorage has the obvious advantage of being lightweight but lacking search capability. When we have too much data and need to use search capabilities, we need a new solution, which is indexedDB.

Local Database Concept

In general, the concept of a database is used for the back end. Front-end engineers rarely have the concept of a database, and many front-end engineers are vague about the concept of a data model. So learning indexedDB may require exposure and understanding of many concepts.

features

Key value pair storage

IndexedDB is a form of unstructured storage that is closer to a database like MongoDB than to a database like MySQL.

A primary key

Each piece of data needs to have a unique field as the primary key, and the primary key cannot be repeated, otherwise an error will be thrown.

asynchronous

All operations in indexedDB are asynchronous, unlike WebStorage. The asynchronous design is designed to prevent blocking of the main thread.

The transaction

When multiple operations are performed at the same time, if one operation fails, all operations are rolled back to the state before the operation. In this way, data consistency is ensured and data inconsistency does not occur when only some data is modified.

The same-origin restrictions

Each web page can only access the database under this domain name. Databases cannot be accessed across domains.

Support the index

Supports indexing, which greatly improves the search efficiency.

Support binary storage

IndexedDB can store binary data such as ArrayBuffer and Blob.

Unlimited storage space

IndexedDB does not fall below 250MB, but there is no theoretical maximum.

object

IndexedDB is a complex set of apis that provide a total of seven apis:

  • Database: IDBDatabase object.
  • Warehouse object: IDBObjectStore object.
  • Index: IDBIndex object.
  • Transaction: IDBTransaction object.
  • Action: IDBRequest object.
  • Pointer: IDBCursor object.
  • Primary key: IDBKeyRange object.

The details of their usage are explained in the actual operation of the API below.

The API operations

Create database & connect database

Connect to the database using the indexedDB.open method. If the database does not exist, a new database is created. This method takes two parameters, the database name and the database version number. It returns an instance of IDBRequest. IDBRequest instances have onSuccess events and OnUpgradenneeded events. The onSuccess event is triggered when the database is successfully opened, in which case the Result attribute on the IDBRequest instance is the IDBDatabase object. Onupgradenneeded events are triggered when the version of the database is larger than the previous version. Onupgradenneeded events execute earlier than onSuccess events.

let db;
let DBRequestLink = window.indexedDB.open('dataBaseName'.1)
DBRequestLink.onsuccess = function(event) {
  console.log('success');
  db = DBRequestLink.result;
};

DBRequestLink.onupgradeneeded = function(event) {
  console.log('upgradeneeded');
};
Copy the code

Create table & create index

The create table is created using the createObjectStore method of the IDBDatabase instance. This method takes two parameters, the first being the table name and the second being the configuration object. The primary key name, increment, and so on can be set in the configuration object. The createObjectStore method returns an instance of IDBObjectStore. An index can be created using an IDBObjectStore instance as createIndex with three arguments. The first two are the name of the index and the name of the object property, respectively. The third is an optional parameter that represents the object and can specify whether the index is unique.

DBOpenRequest.onupgradeneeded = function(event) {
  let db = event.target.result;
  
  let objectStore = db.createObjectStore('person', {
    keyPath: 'id'.autoIncrement: true
  });

  objectStore.createIndex('id'.'id', {
    unique: true
  });
  objectStore.createIndex('name'.'name');
  objectStore.createIndex('age'.'age');
  objectStore.createIndex('sex'.'sex');
};
Copy the code

Increase the data

Each piece of data is a JavaScript object, and the properties correspond to the fields of the table. Obtain the IDBTransaction instance object before storing the data. Obtained through the TRANSACTION method of the IDBDatabase object. This method can accept a maximum of three parameters. The first parameter is the database name and the second parameter is the transaction operation mode. There are three optional parameters: readonly, readWrite, and readWriteFlush. The third parameter is an optional configuration object. The IDBTransaction object returned by the Transaction method has an objectStore method that gets the table, takes the name of the table, and returns an IDBObjectStore object. The Add method of the IDBObjectStore object is used to add data.

let newItem = {
  id: 1.name: 'Joe'.age: 3.sex: 'female'
};
let transaction = db.transaction('dataBaseName'."readwrite");
let objectStore = transaction.objectStore('person');
objectStore.add(newItem);
Copy the code

Query data

Use the GET method, passing in the ID to query.

let transaction = db.transaction('dataBaseName'."readwrite");
let objectStore = transaction.objectStore('person');
let objectStoreRequest = objectStore.get(1);
Copy the code

Modify the data

To modify the data, we first need the object to be modified and the id of the data to be modified. You also need to get the IDBObjectStore object. When the result is obtained, the SUCCESS method will be triggered. The result attribute is the object, and the attribute of the object is replaced. Finally, the new object is put into the table to complete the data modification.

let transaction = db.transaction('dataBaseName'."readwrite");
let newRecord = {
  id: 1.name: 'bill'.age: 5.sex: 'male'
};
let objectStore = transaction.objectStore('person');
let objectStoreRequest = objectStore.get(1);
objectStoreRequest.onsuccess = function(event) {
  var record = objectStoreRequest.result;
  for (let key in newRecord) {
    if (typeofrecord[key] ! ='undefined'|| key ! = ='id') {
      record[key] = newRecord[key];
    }
  }
  objectStore.put(record);
};
Copy the code

Delete the data

Deleting the data is easy, by calling the idbobject. delete method and passing in the ID.

let transaction = db.transaction('dataBaseName'."readwrite");
let objectStore = transaction.objectStore('person');
let objectStoreRequest = objectStore.delete(1);
Copy the code

Use IndexedDB to develop TODOList

To learn how to use IndexedDB more flexibly, here is a TODOList program developed using IndexedDB. You can use this code to implement a TODOList yourself.

Source:

<style>
  * {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
  }

  body.html {
    padding: 0;
    margin: 0;
  }

  body {
    font-family: Helvetica, Arial, sans-serif;
    color: # 545454;
    background: #f7f7f7;
  }

  #page-wrapper {
    width: 550px;
    margin: 2.5 em auto;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0.0.0.0.2);
    border-radius: 3px;
  }

  #new-todo-form {
    padding: 0.5 em;
    background: pink;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
  }

  #new-todo {
    width: 100%;
    padding: 0.5 em;
    font-size: 1em;
    border-radius: 3px;
    border: 0;
    outline: none;
  }

  #todo-items {
    list-style: none;
    padding: 0.5 em 1em;
    margin: 0;
  }

  #todo-items li {
    font-size: 0.9 em;
    padding: 0.5 em;
    background: #fff;
    border-bottom: 1px solid #eee;
    margin: 0.5 em 0;
  }

  input[type="checkbox"] {
    margin-right: 10px;
  }
</style>

<div id="page-wrapper">
  <! -- Form for new Todo Items -->
  <form id="new-todo-form" method="POST" action="#">
    <input
      type="text"
      name="new-todo"
      id="new-todo"
      placeholder="Enter a todo item..."
      required
    />
  </form>

  <! -- Todo Item List -->
  <ul id="todo-items"></ul>
</div>

<script>
  var todoDB = (function () {
    var tDB = {};
    var datastore = null;

    /** * Open a connection to the datastore. */
    tDB.open = function (callback) {
      // Database version.
      var version = 1;

      // Open a connection to the datastore.
      var request = indexedDB.open("todos", version);

      // Handle datastore upgrades.
      request.onupgradeneeded = function (e) {
        var db = e.target.result;

        e.target.transaction.onerror = tDB.onerror;

        // Delete the old datastore.
        if (db.objectStoreNames.contains("todo")) {
          db.deleteObjectStore("todo");
        }

        // Create a new datastore.
        var store = db.createObjectStore("todo", {
          keyPath: "timestamp"}); };// Handle successful datastore access.
      request.onsuccess = function (e) {
        // Get a reference to the DB.
        datastore = e.target.result;

        // Execute the callback.
        callback();
      };

      // Handle errors when opening the datastore.
      request.onerror = tDB.onerror;
    };

    /**
     * Fetch all of the todo items in the datastore.
     * @param {function} callback A function that will be executed once the items
     *                            have been retrieved. Will be passed a param with
     *                            an array of the todo items.
     */
    tDB.fetchTodos = function (callback) {
      var db = datastore;
      var transaction = db.transaction(["todo"]."readwrite");
      var objStore = transaction.objectStore("todo");

      var keyRange = IDBKeyRange.lowerBound(0);
      var cursorRequest = objStore.openCursor(keyRange);

      var todos = [];

      transaction.oncomplete = function (e) {
        // Execute the callback function.
        callback(todos);
      };

      cursorRequest.onsuccess = function (e) {
        var result = e.target.result;

        if(!!!!! result ==false) {
          return;
        }

        todos.push(result.value);

        result.continue();
      };

      cursorRequest.onerror = tDB.onerror;
    };

    /**
     * Create a new todo item.
     * @param {string} text The todo item.
     */
    tDB.createTodo = function (text, callback) {
      // Get a reference to the db.
      var db = datastore;

      // Initiate a new transaction.
      var transaction = db.transaction(["todo"]."readwrite");

      // Get the datastore.
      var objStore = transaction.objectStore("todo");

      // Create a timestamp for the todo item.
      var timestamp = new Date().getTime();

      // Create an object for the todo item.
      var todo = {
        text: text,
        timestamp: timestamp,
      };

      // Create the datastore request.
      var request = objStore.put(todo);

      // Handle a successful datastore put.
      request.onsuccess = function (e) {
        // Execute the callback function.
        callback(todo);
      };

      // Handle errors.
      request.onerror = tDB.onerror;
    };

    /**
     * Delete a todo item.
     * @param {int} id The timestamp (id) of the todo item to be deleted.
     * @param {function} callback A callback function that will be executed if the
     *                            delete is successful.
     */
    tDB.deleteTodo = function (id, callback) {
      var db = datastore;
      var transaction = db.transaction(["todo"]."readwrite");
      var objStore = transaction.objectStore("todo");

      var request = objStore.delete(id);

      request.onsuccess = function (e) {
        callback();
      };

      request.onerror = function (e) {
        console.log(e);
      };
    };

    // Export the tDB object.
    returntDB; }) ();window.onload = function () {
    // Display the todo items.
    todoDB.open(refreshTodos);

    // Get references to the form elements.
    var newTodoForm = document.getElementById("new-todo-form");
    var newTodoInput = document.getElementById("new-todo");

    // Handle new todo item form submissions.
    newTodoForm.onsubmit = function () {
      // Get the todo text.
      var text = newTodoInput.value;

      // Check to make sure the text is not blank (or just spaces).
      if (text.replace(/ /g."") != "") {
        // Create the todo item.
        todoDB.createTodo(text, function (todo) {
          refreshTodos();
        });
      }

      // Reset the input field.
      newTodoInput.value = "";

      // Don't send the form.
      return false;
    };
  };

  function refreshTodos() {
    todoDB.fetchTodos(function (todos) {
      var todoList = document.getElementById("todo-items");
      todoList.innerHTML = "";

      for (var i = 0; i < todos.length; i++) {
        // Read the todo items backwards (most recent first).
        var todo = todos[todos.length - 1 - i];

        var li = document.createElement("li");
        var checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.className = "todo-checkbox";
        checkbox.setAttribute("data-id", todo.timestamp);

        li.appendChild(checkbox);

        var span = document.createElement("span");
        span.innerHTML = todo.text;

        li.appendChild(span);

        todoList.appendChild(li);

        // Setup an event listener for the checkbox.
        checkbox.addEventListener("click".function (e) {
          var id = parseInt(e.target.getAttribute("data-id")); todoDB.deleteTodo(id, refreshTodos); }); }}); }</script>
Copy the code

library

Because the APIS provided by indexedDB are very low level and difficult to use in actual development, we were able to reduce our mental load and improve our development efficiency by using libraries packaged by others such as Localforage and Dexie. Localforage in particular has a number of advantages as we can specify storage solutions in the default order of IndexedDB, WebSQL, LocalStorage. If the browser does not support IndexedDB, it is automatically degraded. And its API is neat and elegant, far less complex than IndexedDB.

conclusion

If the project is new, you are advised to use WebStorage first.

If you need to store large amounts of structured data in specific scenarios, forage, dexie, or Level-JS libraries that are encapsulated based on IndexedDB are recommended.

To store sensitive data, you are advised to use cookies first.