JavaScript shallow copy and deep copy

This article shows the usages of shallow copy and deep copy in JavaScript. Most content is organized from MDN and some new content is added.

Shallow copy

In a shallow copy, the source and copy share the same reference properties.

In JavaScript, all standard built-in object-copy operations create shallow copies rather than deep copies:

Examples

// Shallow copy of an array via spread syntax

const objects = [{a: 1}, {b: 2}];
copy = [...objects];
console.log(copy[0] === objects[0]); // true

// Shallow copy of an object via speras syntax 

const obj = {id: 'a01', values: [101, 102]};
copy = {...obj};
console.log(copy.values === obj.values); // true

Deep copy

In a deep copy, the source and copy are completely independent.

For objects that are serializable, you can alternatively use the structuredClone() method to create deep copies.

Some examples that are not serializable

Calling JSON.stringify() to serialize the objects in those cases will fail. So there’s no way to make deep copies of such objects.

Examples

const objects = [{a: 1}, {b: 2}];

let copy = structuredClone(objects);
console.log(copy[0] === objects[0]); // false

Compatibility of structuredClone()

structuredClone() is a newly added function. Its compatibility on some main browsers:

  • Chrome v98

  • Firefox v94

  • Safari v15.4 (2022)

  • Safari iOS v15.4 (2022)

And it has lower availability in works. To add compatible structuredClone() support in all browsers, see Troubleshooting — StructuredClone is not defined.

Further reading

Reference

  • MDN: Shallow copy

  • MDN: Deep copy

  • MDN: structuredClone()

  • MDN: The structured clone algorithm

    It clones by recursing through the input object while maintaining a map of previously visited references, to avoid infinitely traversing cycles.

    Things that don’t work with structured clone

    • Function objects cannot be duplicated by the structured clone algorithm; attempting to throws a DataCloneError exception.
    • Cloning DOM nodes likewise throws a DataCloneError exception.
    • Certain object properties are not preserved:
    • The lastIndex property of RegExp objects is not preserved.
    • Property descriptors, setters, getters, and similar metadata-like features are not duplicated. For example, if an object is marked readonly with a property descriptor, it will be read/write in the duplicate, since that’s the default.
    • The prototype chain is not walked or duplicated.

    Note: Native Error types can be cloned in Chrome. Firefox can clone DOMException, and is working on the other error types.

JavaScript clipboard access

Accessing the clipboard with old document.execCommand had been superseded by the new Clipboard API. The latter API provides the ability to read from and write to the system clipboard asynchronously. But it is a relatively recent addition to the web standards and may not be fully supported in all browsers, check the browser compatibility before using.

Unlimited clipboard access can be dangerous. Especially reading from it may stole the user’s sensitive data like password. Writing may also bring malicious content to the clipboard that you may paste it somewhere unexpectedly. Therefore there are some restrictions for the methods used to access the clipboard, and reading is more strict.

At the end of the article, we give an example to write an given text to the clipboard. The example tries to work on most browsers.

Deprecated document.execCommand() for clipboard access

Note the document.execCommand() method is no longer recommended and may not be supported in some browsers. But it is still useful in some cases, such as on browsers that have not implemented the Async Clipboard API or not fully implemented.

The document.execCommand() can be used to execute commands like cut, copy and paste to interact with the clipboard. Its effect is analogous to that of a user executing such actions by pressing the keyboard.

Access clipboard with document.execCommand()

An example to execute paste ,copy and cut commands.

HTML

<textarea id="text-a">A</textarea>
<textarea id="text-b">B</textarea>

<button id="copy-button">Copy</button>
<button id="cut-button">Cut</button>
<button id="paste-button">Paste</button>

JavaScript

// Copy content of fromElem or the current selection to the clipboard.
function copy(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("copy");
}

// Remove content of fromElem or the current selectionand 
// and copy it to the clipboard. 
function cut(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("cut");
}

// Paste content from the clipboard to the "toElem" elment.
function paste(toElem) {
    toElem.focus(); // Make it focused.
    // Or use document.execCommand("paste", false, "Some given text");
    document.execCommand("paste");
}

const textA = document.querySelector("#text-a");
const textB = document.querySelector("#text-b");
document.querySelector("#copy-button")
    .addEventListener("click", () => copy(textA));
document.querySelector("#cut-button")
    .addEventListener("click", () => cut(textA));
document.querySelector("#paste-button")
    .addEventListener("click", () => paste(textB));

In the above example, all document.execCommand() calls are inside user-generated event handlers. If "cut" or "copy" is called outside such a handler, it may not work. On Chrome (v104), it just do not work. On Firefox, you may see the following warning:

document.execCommand(‘cut’/‘copy’) was denied because it was not
 called from inside a short running user-generated event handler.

And for "paste", even inside a user-generated event handler, it may be blocked for security.

Other commands

document.execCommand() can also be used to execute commands that the Clipboard API does not have. Like the insertText command which enables you to insert the given plain text at the insertion point programmatically.

Issues with document.execCommand()

  • It is synchronous. If the operation takes long time, it blocks the page.
  • There seems not a clear permission specification for clipboard access with document.execCommand().

The Clipboard API

Besides in a asynchronous way, with the Clipboard API, you can write and read arbitrary data to and from the clipboard, like text, rich text, HTML, images, etc. And it is convenient to copy a given data instead of the current selection to the clipboard.

Access clipboard with the Async Clipboard API

An example to read text and write text from and to the clipboard

HTML

<button id="copy-async-button">Copy async</button>
<button id="paste-async-button">Paste async</button>

JavaScript

function copyAsync(text) {
    if (navigator.clipboard) {
        // It will fail if the codument is not focused: "DOMException: Document is not focused."
        // In this case, click the page to make it focused.
        navigator.clipboard.writeText(text)
            .then(
                () =&gt; console.log("Copy async successful:", text),
                (err) =&gt; console.error("Copy async failed:", err)
            );
    }
}

function pasteAsync() {
    if (navigator.clipboard) {
        // It may fail if the `clipboard-read` permission is denied
        // to the current contenxt.
        // Firefox (at least v99) does not support
        // navigator.clipboard.readText()
        // or navigator.clipboard.read().
        navigator.clipboard.readText()
            .then(
                text =&gt; console.log("Paste async successful:", text),
                err =&gt; console.error("Paste async failed:", err)
            );
    }
}

document.querySelector("#copy-async-button")
    .addEventListener("click", () =&gt; copyAsync("Some text"));
document.querySelector("#paste-async-button")
    .addEventListener("click", pasteAsync);

Note the effect of the example vary depending on the browser and the context (like http or https ). See more in the Browser compatibility section.

The Clipboard API works only when the document is focused, otherwise you will get an exception in the console:

DOMException: Document is not focused.

In this case, click the page to make the document focused.

Permissions

Writing to the clipboard with the Clipboard API needs clipboard-write permission which is defined in Permissions API. This permission is granted automatically to pages when they are in the active tab.

Reading from the clipboard with the Clipboard API needs clipboard-read permission which must be requested. On some browsers of new versions, if the corresponding permission is not granted when reading from or writing to the clipboard, the browsers automatically prompt the user to request the permission.

You can also query whether a permission has been granted to the current context before using the API (clipboard-write and clipboard-read permission names are not supported in Firefox, at least not for v99).

navigator.permissions.query({name: "clipboard-read"})
    .then(result => {
        // state can be one of "granted", "prompted", "denied"
        if (result.state === "granted") { /* ... */}
    });

If the clipboard-read is denied, you will see below exception in the console if you click the paste button in the example.

DOMException: Read permission denied.

Browser compatibility

Check the full Browser compatibility.

Firefox

In the above example, we check the availability of the Clipboard API with navigator.clipboard before using them.

However, that is not enough. Firefox (v99) supports navigator.clibpard.writeText() but not navigator.clibpard.readText(). You need to put the call inside a try/catch block to work with most browsers.

And the browser does not support permission names of clipboard-write and clipboard-read. You will get an exception like below when you query it with Permission API:

Uncaught (in promise) TypeError: 'clipboard-read' (value of 'name'
 member of PermissionDescriptor) is not a valid value for enumeration
 PermissionName.

Chrome

Chrome (v104) does not prompt the user for permission when a permission like clibpard-read is denied. It just fails with an exception.

Safari

Safari use the WebKit browser engine. WebKit supports four MIME type representations: "text/plain""text/html""text/uri-list", and "image/png".

The Clipboard API is not supported on Safari until Safari V13.1 (year of 2020) and Safari iOS V13.4. You need special process for navigator.clibboard.write() on Safari. Below is an example to copy image data to the clibboard.

An example to copy an image for Safari and other browsers

Clipboard.write() writes arbitrary datato the clipboard., such as images,

HTML

<button id="paste-image-async-button">Paste image async</button>

JavaScript:

function copyImageAsync() {
    fetch("a.png")
        .then(response =&gt; {
            if (!response.ok) { // Check whether it is ok
                throw new Error(`HTTP error! Status: ${response.status}`);
            }

            return response.blob();
        })
        .then(blob =&gt; {
            console.log('blob.type =', blob.type);

            // On Safari, each ClipboardItem is initialized with
            // a mapping of MIME type to Promise.
            if (navigator.userAgent.includes("Safari")) { // On safari,
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: Promise.resolve(blob)
                    })
                ]);

            } else {
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: blob
                    })
                ]);
            }
        }).catch(err =&gt; console.error(err));;
}
document.querySelector("#paste-image-async-button")
    .addEventListener("click", copyImageAsync);

Note special process for ClipboardItem on Safari

WebKit’s Async Clipboard API: Each ClipboardItem is initialized with a mapping of MIME type to Promise (see ).

See more at webkit: Async Clipboard API. It also specify how to use the new Async Clipboard API and old document.execCommand("copy") on Safari.

Conclusion

Now you have known the main differences between document.execCommand() and the Clipboard API. To be compatible with most browsers, we need to use both methods to work with the clipboard access.

Below example is a variant of example in webkit: Async Clipboard API. It writes a given text to the clipboard which tries to be compatible to as many browsers as possible.

HTML

<button id="copy-given-text-button">Copy given text</button>

JavaScript

// Copy the given text to the clipboard
function copyGivenText(text) {
    try {
        if (!navigator.clipboard) {
            // First create a textarea element to hold the given text
            // and set its content being selected.
            // A他 remove it after copping the selection to the clipboard.

            let textarea = document.createElement("textarea");
            textarea.style.opacity = "0";
            textarea.style.position = "fixed";
            textarea.value = text;
            document.body.appendChild(textarea);

            textarea.focus();
            textarea.setSelectionRange(0, textarea.value.length);
            document.execCommand("copy");
            console.log("Copy successful");

            textarea.remove();
        } else {
            navigator.clipboard.writeText(text)
                .then(
                    () => console.log("Copy successful"),
                    () => console.log("Copy failed")
                );
        }
    } catch (err) {
        console.error(err);
    }
}
document.querySelector("#copy-given-text-button")
    .addEventListener("click", () => copyGivenText("Given text"));

Reference

  • MDN: Clipboard API

    > Note: This API is not available in Web Workers (not exposed via WorkerNavigator).

  • MDN: Interact with the clipboard

    The Clipboard API is only available to Secure Contexts (An active tab in the browser is also considered to be a secure context).

    Using the Clipboard API in an browser extension requires the permission "clipboardRead" or "clipboardWrite" in the manifest.json file.

  • web.dev: Unblocking clipppbard acess

    To use the Clipboard API in iframes, you need to pass either or both clipboard-read or clipboard-write like:


  • WebKit’s Async Clipboard API

    > Note that both clipboard.write and clipboard.writeText are asynchronous. If you attempt to write to the clipboard while a prior clipboard writing invocation is still pending, the previous invocation will immediately reject, and the new content will be written to the clipboard.

    Sanitization

    Both "text/html" and "image/png" data is sanitized before writing to the pasteboard.

    Similar to writing data, reading data from the system clipboard involves sanitization to prevent users from unknowingly exposing sensitive information.

  • w3.org: Async Clipboard API

JavaScript troubleshooting — structuredClone is not defined

Uncaught ReferenceError: StructuredClone is not defined

The error is because structuredClone() is not supported in the browser. For example, structuredClone() is not supported on Safari iOS until v15.4(Released in 2022) and it is not supported on IE at all.

To add compatible structuredClone() support in all browsers, you can define a manual deep copy function or use an existing one of a JavaScript library, like copyDeep of Lodash, clone() of Ramda.

Solution 1 — use user defined deep copy function

A user-defined deep copy function which is compatible with IE browser:

if (!window.structuredClone) {
    function myDeepCopy(value) {
        if (Array.isArray(value)) {
            var count = value.length;
            var arr = new Array(count);
            for (var i = 0; i < count; i++) {
                arr[i] = myDeepCopy(value[i]);
            }

            return arr;
        } else if (typeof value === 'object') {
            var obj = {};
            for (var prop in value) {
                obj[prop] = myDeepCopy(value[prop]);
            }

            return obj;
        } else { // Primitive value
            return value;
        }
    }

    window.structuredClone = myDeepCopy;
}

Solution 2 — use cloneDeep() of Lodash

Lodash is a modern JavaScript utility library. It is released under the MIT license & supports modern environments.

cloneDeep(value) creates a deep clone of value.

Example (Use lodash as a ES6 module):

// In ES6 module style:
import _ from 'lodash';
// Or you can just import the function you need
// import cloneDeep from "lodash/cloneDeep";

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

Further reading

Reference

JavaScript troubleshooting — Failed to read the loacalStorage

Although localStorage API is available in current versions of all major browsers, the user may still encounter the exception Failed to read the 'localStorage' for localStorate has been disabled in browser settings. Like on Chrome, the user can check “Block cookies” option in the privacy settings to make localStorage is blocked too.

Therefore it is necessary to detect the availability of localStorage to fix the issue.

Uncaught DOMException: Failed to read the ‘localStorage’ property from ‘Window’

The full error message is:

Uncaught DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.

Solution

MDN provides a  a function that detects whether localStorage is both supported and available:

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

Then you access localStorage only if it is available.

let isStorageAvailable = storageAvailable();
if (isStorageAvailable) {
    // Access localStorage
}

Further reading

  • JavaScript storage

    This post introduces modern Web data storage like sessionStorage, localStorage, IndexedDB, Cache, etc, as well as their applicable scenarios.

Reference

JavaScript data storage — localStorage, IndexedDB and CacheStorage

This article gives all you want to know about different data storage including localStorage, sessionStorage, IndexedDB and Cache. After reading this post, you will get to know the differences among them, which storage should you use, how to use them, how much data can I store, etc.

Overview

An overview of the storage types.

  • Web Storage
    • sessionStorage Temporary session data

    • Store key/value pair only for a session, data expires when browser(or tab) is closed.

    • The keys and values are always in the UTF-16 string format.

    • localStorage Small data

    • Store key/value pair persistently.

    • The keys and values are always in the UTF-16 string format.

    • Useful for storing small amounts of data.

  • [IndexedDB](IndexedDB API – Web APIs | MDN) Large data

    • A transactional database system,

    • Powerful but complicated for simple cases.

    • Useful for storing large amounts of structured data.

  • Cache Resource caching

    • Store Request / Response object pairs, representing HTTP requests and responses.
    • Useful in URL addressable resource caching, like assets, HTTP API responses.

Note

Cookies should not be used for storage. They are sent with every HTTP request. If storing data with it will significantly increase the size of a HTTP request.

Deprecated API for storage

Differences among data storage types

Here are differences among the 4 types of data storage.

Storage Expire Asynchronous Size limit Security Web workers
sessionStorage ✓ (Yes) ✗ (no) 5MB Same tab ✗ (No)
localStorage ✗ (No)* ✗ (No) Same origin ✗ (No)
IndexedDB ✗ (No)* ✓ (Yes) Large Same origin ✓ (Yes)
Cache ✗ (No)* ✓ (Yes) ✓ (Yes)

More about expire

If storage limit is reached, data may be cleared out depending on storage type and the browser.

For Safari, data will be deleted after 7 days of use.

webkit: 7-Day Cap on All Script-Writeable Storage

In iOS and iPadOS 13.4 and Safari 13.1 on macOS, it will delete all of a website’s script-writable storage after seven days of Safari use without user interaction on the site. These are the script-writable storage forms affected (excluding some legacy website data types):

  • Indexed DB
  • LocalStorage
  • Media keys
  • SessionStorage
  • Service Worker registrations and cache

More about security

Access to Web Storage from third-party IFrames is denied if the user has disabled third-party cookies (Firefox implements this behavior from version 43 onwards.)

Note

Cookies use a separate definition of origins compared to that used by Web Storage and IndexedDB. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix.

Storage limit

The global limit for all kinds of storage is dynamic and depends on the browser. According to Storage for the web:

  • Firefox:  50% of free disk space.

  • Chrome: 80% of total disk space. An origin can use up to 60%

  • Safari (both desktop and mobile): appears to allow about 1GB.

What happen if the storage limit is reached?

Depending on the browser, data eviction may be triggered or a QuotaExceededError may be thrown out. The data eviction is based on an LRU policy — the least recently used origin will be deleted first, then the next one, until the browser is no longer over the limit.

See MDN: Browser storage limits and eviction criteria for more information.

Persistent storage

Storage for the web:

You can request persistent storage for your site to protect critical user or application data. Unless the user removes it, you can protect it from eviction.

// Check if site's storage has been marked as persistent
if (navigator.storage &amp;&amp; navigator.storage.persist) {
    const isPersisted = await navigator.storage.persisted();
    console.log(`Persisted storage granted: ${isPersisted}`);
}

// Request persistent storage for site
if (navigator.storage &amp;&amp; navigator.storage.persist) {
    const isPersisted = await navigator.storage.persist();
    console.log(`Persisted storage granted: ${isPersisted}`);
}

How much storage space is taken and available?

In many browsers, you can use [StorageManager API](StorageManager.estimate() – Web APIs | MDN) to asks the Storage Manager for how much storage the current origin takes up (usage), and how much space is available (quota).

This method operates asynchronously, so it returns a Promise which resolves once the information is available. The promise’s fulfillment handler is called with an object containing the usage and quota data.

Example

 if (navigator.storage && navigator.storage.estimate) {
    navigator.storage.estimate().then(function(estimate) {
        console.log('You are currently using about '
            + (estimate.usage / estimate.quota * 100).toFixed(2)
            + '% of your available storage.');
        console.log('You can write ' + (estimate.quota - estimate.usage)
            + ' more bytes.');
    });
}

Where is the storage data stored?

The actual storage is stored at a SQLite file under directory /.

MDN: Browser storage limits and eviction criteria:

Where is the data stored?

Each storage type represents a separate repository. Here’s the actual mapping to directories under a user’s Firefox profile (other browsers may differ slightly):

  • /storage — the main top-level directory for storages maintained by the quota manager (see below)
  • /storage/permanent — persistent data storage repository
  • /storage/temporary — temporary data storage repository
  • /storage/default — default data storage repository

The profile folder for browsers:

  • Firefox

    In Firefox, enter about:support in the URL bar to open the “Troubleshooting Information” tab. You will find the profile folder is displayed in the “Application Basics” table like below (Firefox v99/Win10):

Profile Folder  C:UsersAppDataRoamingMozillaFirefoxProfileskxx.default-release-xxx

Or you can open the “Troubleshooting Information” page by

Firefox menu &gt; Help &gt; More Troubleshooting Information.

See more about what information is stored in the profile at Firefox Profile.

An example of a localStorage file (Firefox v99/win10)

/
|- ...
|- cookies.sqlite // ---- Cookies
|- storage/
|- archives/
|- default/ // ---- Kinds of storage for sites
|- https+++xx1.com/
|- cache/
|- idb/
|- ls/ // ---- LocalStorage for xx1.com is saved here
|- data.sqlite // You can open it with a SQLite browser
|- ...
|- permanent/
|- temporary/

In the example above, the LocalStorage data for https://xx1.com` is stored in a SQLite database file --data.sqlite`. You can open it with a SQLite browser. You will see the same data as you inspect it in the DevTools (in Storage tab) of Firefox.

  • Chrome

    The profile for chrome:

    C:UsersAppDataLocalGoogleChromeUser Data

Storage inspect in DevTools

In DevTools, you can inspect the different storage types and clear data. Chrome v88 provides a feature for you to simulate storage quota. See Storage inspect (The content is also quoted in the end of the article) for more details.

sessionStorage

Opening multiple tabs/windows with the same URL creates sessionStorage for each tab/window. It means sessionStorage does not share between multiple tabs/windows.

Availability detecting

See the same section of localStorage.

Basic usage example

// Save data to sessionStorage
sessionStorage.setItem('key', 'value');

// Get saved data from sessionStorage
let data = sessionStorage.getItem('key');

// Remove saved data from sessionStorage
sessionStorage.removeItem('key');

// Remove all saved data from sessionStorage
sessionStorage.clear();

To write/read some data rather than string, use JSON.stringify() to convert it to string and use JSON.parse() to convert it from string.

localStorage

localStorage is similar to sessionStorage, except that while localStorage data has no expiration time.

Availability detecting

Before using localStorage, it is necessary to detect whether it is available, because it may throw exceptions like Failed to read the loacalStorag in various situations. Below are some of them:

  • localStorage is not supported in an early version of a browser.

  • The user disables web storage in the browser settings, like the cookie is blocked on Chrome.

  • In a browser’s private browsing mode.

MDN gives a function that detects whether localStorage or sessionStorage is both supported and available:

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

And here is how you would use it:

if (storageAvailable('localStorage')) {
  // Yippee! We can use localStorage awesomeness
}
else {
  // Too bad, no localStorage for us
}

Call storageAvailable('sessionStorage') for sessionStorage.

Basic usage example

// Add a localStorage item
localStorage.setItem('myCat', 'Tom');

// Read the localStorage item
const cat = localStorage.getItem('myCat');

// Remove the localStorage item.
localStorage.removeItem('myCat');

To write/read some data rather than string, use JSON.stringify() to convert it to string and use JSON.parse() to convert it from string.

IndexedDB

IndexedDB is a way for you to persistently store structured data , including files/blobs, inside a user’s browser. It can work both online and offline.

Usage

IndexedDB is a low-level API which means it is not easy to use. There are some wrappers of IndexedDB to improve usability, below are some:

  • localForage: A Polyfill providing a simple name:value syntax for client-side data storage, which uses IndexedDB in the background, but falls back to WebSQL and then localStorage in browsers that don’t support IndexedDB.

  • idb: A tiny (~1.15k) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability.

  • Dexie.js: A wrapper for IndexedDB that allows much faster code development via nice, simple syntax.

  • idb-keyval: A super-simple-small (~600B) promise-based keyval store implemented with IndexedDB

  • sifrr-storage: A small (~2kB) promise based library for client side key-value storage. Works with IndexedDB, localStorage, WebSQL, Cookies. Can automatically use supported storage available based on priority.

  • JsStore: A simple and advanced IndexedDB wrapper having SQL like syntax.

MDN provides a full IndexedDB example:

Availability detecting

if (window.indexedDB) {
    console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}

Cache

Cache is designed to store Request / Response object pairs. It is useful in resource caching.

An origin can have multiple, named Cache objects.

Size limit

Each browser has a hard limit on the amount of cache storage that a given origin can use.

You are responsible for periodically purging cache entries. See Deleting old caches for more information.

Availability detecting

if (window.caches) {
    // ...
}

Basic usage example

Use CacheStorage.open() to open a specific named Cache object and then call any of the Cache methods to maintain the Cache.

You can access CacheStorage through the global caches property.

API

// Open a cache
// If the specified Cache does not exist, a new cache is created
// with that cacheName and a Promise that resolves to this new
// Cache object is returned.
caches.open('my-cache').then(cache => {
    // Do things with the cache
});

// Add resource to the cache
caches.open('my-cache').then(cache => {
    fetch(url).then(function(response) {
        if (response.ok) {
            cache.put(url, response);
        }
    });
});

// Obtain the resouce associated with an URL and add it to the cache.
// add(url) takes a URL, retrieves it, and adds the resulting response
// object to the given cache.
caches.open('my-cache').then(cache => {
    cache.add(url);
});

// Retrieve a response associated with a request
caches.open('my-cache').then(cache => {
    cache.match(url).then(res => {
        //...
    });
});

// Delete
caches.open('my-cache').then(cache => {
    cache.delete(ur);
});

// Delete cache
caches.delete('my-cache').then(() => {
    // Deleted
});

Example

A simple example variant from MDN:

async function getCacheFirst(request) {
    var cacheAvailable = window.caches ? true : false;
    var response = await caches.match(request).catch((err) => {
        cacheAvailable = false;
        return null;
    });

    if (response) {
        return response;
    }

    // Not found in the caches or the caches are blocked.
    return await fetch(request).then(res => {
        if (cacheAvailable) {
            var res2 = res.clone();
            caches.open('v1').then((cache) => {
                cache.put(request, res2);
            });
        }

        return res;
    }).catch(err => {
        return null;
    });
}

In the code above, clone() is needed because put() consumes the response body.

Note:

The default example code from MDN has issues if not updated. It fails with TypeError or DOMException.

  • If the caches are available (not blocked by the browser), then it fails at cache.put(event.request, response) with Uncaught TypeError because the parameter response is null , it has not been saved in the cache yet.

  • If the caches are blocked by the browser (Like the cookies are blocked for the website in the settings of Chrome), it runs the first catch statement and then fails at caches.open('v1') in the then block with Uncaught DOMException because the cache is not allowed to access.

const cachedResponse = caches
  .match(event.request)
  .catch(() =&gt; fetch(event.request))
  .then((r) =&gt; {
    response = r;
    caches.open("v1").then((cache) =&gt; {
      cache.put(event.request, response);
    });
    return response.clone();
  })
  .catch(() =&gt; caches.match("/gallery/myLittleVader.jpg"));

You can download the whole simple-service-worker source code from simple-service-worker example.

MDN: Cache – Example is another example

Reference

  • MDN: Web Storage API
    • sessionStorage

    • Storage limit is larger than a cookie (at most 5MB).

    • localStorage

  • MDN: Using the Web Storage API

  • MDN: Using IndexedDB

  • MDN: IndexedDB API

  • MDN: Browser storage limits and eviction criteria

    > Talks about:
    >
    > – How much space to allocate to web data storage?
    >
    > – What to delete when that limit is reached?
    >
    > – Where is the data stored?
    >
    > Each storage type represents a separate repository. Here’s the actual mapping to directories under a user’s Firefox profile (other browsers may differ slightly):

  • MDN: Cache

  • MDN: CacheStorage

    The CacheStorage interface represents the storage for Cache objects.

  • MDN: Cross-Origin Resource Sharing (CORS)

  • MDN: Same-origin policy

    Set the value of document.domain…, and changing the origin in this way does not affect the origin checks used by many Web APIs (e.g. localStorageindexedDBBroadcastChannelSharedWorker)

    > ## Cross-origin data storage access
    >
    > Access to data stored in the browser such as Web Storage and IndexedDB are separated by origin. Each origin gets its own separate storage, and JavaScript in one origin cannot read from or write to the storage belonging to another origin.
    >
    > Cookies use a separate definition of origins. A page can set a cookie for its own domain or any parent domain, as long as the parent domain is not a public suffix.

  • Storage for the web

    sessionStorage and localStorage are not accessible from web workers or service workers.

    > ### Storage inspect
    >
    > During development, you can use your browser’s DevTools to inspect the different storage types, and easily clear all stored data.
    >
    > A new feature was added in Chrome 88 that lets you override the site’s storage quota in the Storage Pane. This feature gives you the ability to simulate different devices and test the behavior of your apps in low disk availability scenarios. Go to Application then Storage, enable the Simulate custom storage quota checkbox, and enter any valid number to simulate the storage quota.
    >
    > While working on this article, I wrote a simple tool to attempt to quickly use as much storage as possible. It’s a quick and easy way to experiment with different storage mechanisms, and see what happens when you use all of your quota.