CINXE.COM

客户端存储 - 学习 Web 开发 | MDN

<!doctype html><html lang="en-US" prefix="og: https://ogp.me/ns#"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="https://developer.mozilla.org/favicon-48x48.bc390275e955dacb2e65.png"/><link rel="apple-touch-icon" href="https://developer.mozilla.org/apple-touch-icon.528534bba673c38049c2.png"/><meta name="theme-color" content="#ffffff"/><link rel="manifest" href="https://developer.mozilla.org/manifest.f42880861b394dd4dc9b.json"/><link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="MDN Web Docs"/><title>客户端存储 - 学习 Web 开发 | MDN</title><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/de/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="de"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="en"/><link rel="alternate" title="Almacenamiento del lado cliente" href="https://developer.mozilla.org/es/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="es"/><link rel="alternate" title="Stockage côté client" href="https://developer.mozilla.org/fr/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="fr"/><link rel="alternate" title="クライアント側ストレージ" href="https://developer.mozilla.org/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="ja"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/pt-BR/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="pt"/><link rel="alternate" title="Client-side storage" href="https://developer.mozilla.org/ru/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="ru"/><link rel="alternate" title="客户端存储" href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" hrefLang="zh"/><link rel="preload" as="font" type="font/woff2" href="/static/media/Inter.var.c2fe3cb2b7c746f7966a.woff2" crossorigin=""/><link rel="alternate" type="application/rss+xml" title="MDN Blog RSS Feed" href="https://developer.mozilla.org/en-US/blog/rss.xml" hrefLang="en"/><meta name="description" content="现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。"/><meta property="og:url" content="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage"/><meta property="og:title" content="客户端存储 - 学习 Web 开发 | MDN"/><meta property="og:type" content="website"/><meta property="og:locale" content="zh_CN"/><meta property="og:description" content="现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。"/><meta property="og:image" content="https://developer.mozilla.org/mdn-social-share.d893525a4fb5fb1f67a2.png"/><meta property="og:image:type" content="image/png"/><meta property="og:image:height" content="1080"/><meta property="og:image:width" content="1920"/><meta property="og:image:alt" content="The MDN Web Docs logo, featuring a blue accent color, displayed on a solid black background."/><meta property="og:site_name" content="MDN Web Docs"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:creator" content="MozDevNet"/><link rel="canonical" href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage"/><style media="print">.article-actions-container,.document-toc-container,.language-menu,.main-menu-toggle,.on-github,.page-footer,.place,.sidebar,.top-banner,.top-navigation-main,ul.prev-next{display:none!important}.main-page-content,.main-page-content pre{padding:2px}.main-page-content pre{border-left-width:2px}</style><script src="/static/js/gtag.js" defer=""></script><script defer="" src="/static/js/main.1b60bff1.js"></script><link href="/static/css/main.959b5ea9.css" rel="stylesheet"/></head><body><script>if(document.body.addEventListener("load",(t=>{t.target.classList.contains("interactive")&&t.target.setAttribute("data-readystate","complete")}),{capture:!0}),window&&document.documentElement){const t={light:"#ffffff",dark:"#1b1b1b"};try{const e=window.localStorage.getItem("theme");e&&(document.documentElement.className=e,document.documentElement.style.backgroundColor=t[e]);const o=window.localStorage.getItem("nop");o&&(document.documentElement.dataset.nop=o)}catch(t){console.warn("Unable to read theme from localStorage",t)}}</script><div id="root"><ul id="nav-access" class="a11y-nav"><li><a id="skip-main" href="#content">Skip to main content</a></li><li><a id="skip-search" href="#top-nav-search-input">Skip to search</a></li><li><a id="skip-select-language" href="#languages-switcher-button">Skip to select language</a></li></ul><div class="page-wrapper category-javascript document-page"><div class="top-banner loading"><section class="place top container"></section></div><div class="sticky-header-container"><header class="top-navigation "><div class="container "><div class="top-navigation-wrap"><a href="/zh-CN/" class="logo" aria-label="MDN homepage"><svg id="mdn-docs-logo" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 694.9 104.4" style="enable-background:new 0 0 694.9 104.4" xml:space="preserve" role="img"><title>MDN Web Docs</title><path d="M40.3 0 11.7 92.1H0L28.5 0h11.8zm10.4 0v92.1H40.3V0h10.4zM91 0 62.5 92.1H50.8L79.3 0H91zm10.4 0v92.1H91V0h10.4z" class="logo-m"></path><path d="M627.9 95.6h67v8.8h-67v-8.8z" class="logo-_"></path><path d="M367 42h-4l-10.7 30.8h-5.5l-10.8-26h-.4l-10.5 26h-5.2L308.7 42h-3.8v-5.6H323V42h-6.5l6.8 20.4h.4l10.3-26h4.7l11.2 26h.5l5.7-20.3h-6.2v-5.6H367V42zm34.9 20c-.4 3.2-2 5.9-4.7 8.2-2.8 2.3-6.5 3.4-11.3 3.4-5.4 0-9.7-1.6-13.1-4.7-3.3-3.2-5-7.7-5-13.7 0-5.7 1.6-10.3 4.7-14s7.4-5.5 12.9-5.5c5.1 0 9.1 1.6 11.9 4.7s4.3 6.9 4.3 11.3c0 1.5-.2 3-.5 4.7h-25.6c.3 7.7 4 11.6 10.9 11.6 2.9 0 5.1-.7 6.5-2 1.5-1.4 2.5-3 3-4.9l6 .9zM394 51.3c.2-2.4-.4-4.7-1.8-6.9s-3.8-3.3-7-3.3c-3.1 0-5.3 1-6.9 3-1.5 2-2.5 4.4-2.8 7.2H394zm51 2.4c0 5-1.3 9.5-4 13.7s-6.9 6.2-12.7 6.2c-6 0-10.3-2.2-12.7-6.7-.1.4-.2 1.4-.4 2.9s-.3 2.5-.4 2.9h-7.3c.3-1.7.6-3.5.8-5.3.3-1.8.4-3.7.4-5.5V22.3h-6v-5.6H416v27c1.1-2.2 2.7-4.1 4.7-5.7 2-1.6 4.8-2.4 8.4-2.4 4.6 0 8.4 1.6 11.4 4.7 3 3.2 4.5 7.6 4.5 13.4zm-7.7.6c0-4.2-1-7.4-3-9.5-2-2.2-4.4-3.3-7.4-3.3-3.4 0-6 1.2-8 3.7-1.9 2.4-2.9 5-3 7.7V57c0 3 1 5.6 3 7.7s4.5 3.1 7.6 3.1c3.6 0 6.3-1.3 8.1-3.9 1.8-2.7 2.7-5.9 2.7-9.6zm69.2 18.5h-13.2v-7.2c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2 5.7 0 9.8 2.2 12.3 6.5V22.3h-8.6v-5.6h15.8v50.6h6v5.5zM493.2 56v-4.4c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm53.1-1.4c0 5.6-1.8 10.2-5.3 13.7s-8.2 5.3-13.9 5.3-10.1-1.7-13.4-5.1c-3.3-3.4-5-7.9-5-13.5 0-5.3 1.6-9.9 4.7-13.7 3.2-3.8 7.9-5.7 14.2-5.7s11 1.9 14.1 5.7c3 3.7 4.6 8.1 4.6 13.3zm-7.7-.2c0-4-1-7.2-3-9.5s-4.8-3.5-8.2-3.5c-3.6 0-6.4 1.2-8.3 3.7s-2.9 5.6-2.9 9.5c0 3.7.9 6.8 2.8 9.4 1.9 2.6 4.6 3.9 8.3 3.9 3.6 0 6.4-1.3 8.4-3.8 1.9-2.6 2.9-5.8 2.9-9.7zm45 5.8c-.4 3.2-1.9 6.3-4.4 9.1-2.5 2.9-6.4 4.3-11.8 4.3-5.2 0-9.4-1.6-12.6-4.8-3.2-3.2-4.8-7.7-4.8-13.7 0-5.5 1.6-10.1 4.7-13.9 3.2-3.8 7.6-5.7 13.2-5.7 2.3 0 4.6.3 6.7.8 2.2.5 4.2 1.5 6.2 2.9l1.5 9.5-5.9.7-1.3-6.1c-2.1-1.2-4.5-1.8-7.2-1.8-3.5 0-6.1 1.2-7.7 3.7-1.7 2.5-2.5 5.7-2.5 9.6 0 4.1.9 7.3 2.7 9.5 1.8 2.3 4.4 3.4 7.8 3.4 5.2 0 8.2-2.9 9.2-8.8l6.2 1.3zm34.7 1.9c0 3.6-1.5 6.5-4.6 8.5s-7 3-11.7 3c-5.7 0-10.6-1.2-14.6-3.6l1.2-8.8 5.7.6-.2 4.7c1.1.5 2.3.9 3.6 1.1s2.6.3 3.9.3c2.4 0 4.5-.4 6.5-1.3 1.9-.9 2.9-2.2 2.9-4.1 0-1.8-.8-3.1-2.3-3.8s-3.5-1.3-5.8-1.7-4.6-.9-6.9-1.4c-2.3-.6-4.2-1.6-5.7-2.9-1.6-1.4-2.3-3.5-2.3-6.3 0-4.1 1.5-6.9 4.6-8.5s6.4-2.4 9.9-2.4c2.6 0 5 .3 7.2.9 2.2.6 4.3 1.4 6.1 2.4l.8 8.8-5.8.7-.8-5.7c-2.3-1-4.7-1.6-7.2-1.6-2.1 0-3.7.4-5.1 1.1-1.3.8-2 2-2 3.8 0 1.7.8 2.9 2.3 3.6 1.5.7 3.4 1.2 5.7 1.6 2.2.4 4.5.8 6.7 1.4 2.2.6 4.1 1.6 5.7 3 1.4 1.6 2.2 3.7 2.2 6.6zM197.6 73.2h-17.1v-5.5h3.8V51.9c0-3.7-.7-6.3-2.1-7.9-1.4-1.6-3.3-2.3-5.7-2.3-3.2 0-5.6 1.1-7.2 3.4s-2.4 4.6-2.5 6.9v15.6h6v5.5h-17.1v-5.5h3.8V51.9c0-3.8-.7-6.4-2.1-7.9-1.4-1.5-3.3-2.3-5.6-2.3-3.2 0-5.5 1.1-7.2 3.3-1.6 2.2-2.4 4.5-2.5 6.9v15.8h6.9v5.5h-20.2v-5.5h6V42.4h-6.1v-5.6h13.4v6.4c1.2-2.1 2.7-3.8 4.7-5.2 2-1.3 4.4-2 7.3-2s5.3.7 7.5 2.1c2.2 1.4 3.7 3.5 4.5 6.4 1.1-2.5 2.7-4.5 4.9-6.1s4.8-2.4 7.9-2.4c3.5 0 6.5 1.1 8.9 3.3s3.7 5.6 3.7 10.2v18.2h6.1v5.5zm42.5 0h-13.2V66c-1.2 2.2-2.8 4.1-4.9 5.6-2.1 1.6-4.8 2.4-8.3 2.4-4.8 0-8.7-1.6-11.6-4.9-2.9-3.2-4.3-7.7-4.3-13.3 0-5 1.3-9.6 4-13.7 2.6-4.1 6.9-6.2 12.8-6.2s9.8 2.2 12.3 6.5V22.7h-8.6v-5.6h15.8v50.6h6v5.5zm-13.3-16.8V52c-.1-3-1.2-5.5-3.2-7.3s-4.4-2.8-7.2-2.8c-3.6 0-6.3 1.3-8.2 3.9-1.9 2.6-2.8 5.8-2.8 9.6 0 4.1 1 7.3 3 9.5s4.5 3.3 7.4 3.3c3.2 0 5.8-1.3 7.8-3.8 2.1-2.6 3.1-5.3 3.2-8zm61.5 16.8H269v-5.5h6V51.9c0-3.7-.7-6.3-2.2-7.9-1.4-1.6-3.4-2.3-5.7-2.3-3.1 0-5.6 1-7.4 3s-2.8 4.4-2.9 7v15.9h6v5.5h-19.3v-5.5h6V42.4h-6.2v-5.6h13.6V43c2.6-4.6 6.8-6.9 12.7-6.9 3.6 0 6.7 1.1 9.2 3.3s3.7 5.6 3.7 10.2v18.2h6v5.4h-.2z" class="logo-text"></path></svg></a><button title="Open main menu" type="button" class="button action has-icon main-menu-toggle" aria-haspopup="menu" aria-label="Open main menu" aria-expanded="false"><span class="button-wrap"><span class="icon icon-menu "></span><span class="visually-hidden">Open main menu</span></span></button></div><div class="top-navigation-main"><nav class="main-nav" aria-label="Main menu"><ul class="main-menu nojs"><li class="top-level-entry-container "><button type="button" id="references-button" class="top-level-entry menu-toggle" aria-controls="references-menu" aria-expanded="false">References</button><a href="/zh-CN/docs/Web" class="top-level-entry">References</a><ul id="references-menu" class="submenu references hidden inline-submenu-lg" aria-labelledby="references-button"><li class="apis-link-container mobile-only "><a href="/zh-CN/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li><li class="html-link-container "><a href="/zh-CN/docs/Web/HTML" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Structure of content on the web</p></div></a></li><li class="css-link-container "><a href="/zh-CN/docs/Web/CSS" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Code used to describe document style</p></div></a></li><li class="javascript-link-container "><a href="/zh-CN/docs/Web/JavaScript" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">General-purpose scripting language</p></div></a></li><li class="http-link-container "><a href="/zh-CN/docs/Web/HTTP" class="submenu-item "><div class="submenu-icon http"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP</div><p class="submenu-item-description">Protocol for transmitting web resources</p></div></a></li><li class="apis-link-container "><a href="/zh-CN/docs/Web/API" class="submenu-item "><div class="submenu-icon apis"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web APIs</div><p class="submenu-item-description">Interfaces for building web applications</p></div></a></li><li class="apis-link-container "><a href="/zh-CN/docs/Mozilla/Add-ons/WebExtensions" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Extensions</div><p class="submenu-item-description">Developing extensions for web browsers</p></div></a></li><li class="apis-link-container desktop-only "><a href="/zh-CN/docs/Web" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Web Technology</div><p class="submenu-item-description">Web technology reference for developers</p></div></a></li></ul></li><li class="top-level-entry-container active"><button type="button" id="guides-button" class="top-level-entry menu-toggle" aria-controls="guides-menu" aria-expanded="false">Guides</button><a href="/zh-CN/docs/Learn" class="top-level-entry">Guides</a><ul id="guides-menu" class="submenu guides hidden inline-submenu-lg" aria-labelledby="guides-button"><li class="apis-link-container mobile-only "><a href="/zh-CN/docs/Learn" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview / MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="apis-link-container desktop-only "><a href="/zh-CN/docs/Learn" class="submenu-item "><div class="submenu-icon learn"></div><div class="submenu-content-container"><div class="submenu-item-heading">MDN Learning Area</div><p class="submenu-item-description">Learn web development</p></div></a></li><li class="html-link-container "><a href="/zh-CN/docs/Learn/HTML" class="submenu-item "><div class="submenu-icon html"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTML</div><p class="submenu-item-description">Learn to structure web content with HTML</p></div></a></li><li class="css-link-container "><a href="/zh-CN/docs/Learn/CSS" class="submenu-item "><div class="submenu-icon css"></div><div class="submenu-content-container"><div class="submenu-item-heading">CSS</div><p class="submenu-item-description">Learn to style content using CSS</p></div></a></li><li class="javascript-link-container "><a href="/zh-CN/docs/Learn/JavaScript" class="submenu-item "><div class="submenu-icon javascript"></div><div class="submenu-content-container"><div class="submenu-item-heading">JavaScript</div><p class="submenu-item-description">Learn to run scripts in the browser</p></div></a></li><li class=" "><a href="/zh-CN/docs/Web/Accessibility" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Accessibility</div><p class="submenu-item-description">Learn to make the web accessible to all</p></div></a></li></ul></li><li class="top-level-entry-container "><button type="button" id="mdn-plus-button" class="top-level-entry menu-toggle" aria-controls="mdn-plus-menu" aria-expanded="false">Plus</button><a href="/zh-CN/plus" class="top-level-entry">Plus</a><ul id="mdn-plus-menu" class="submenu mdn-plus hidden inline-submenu-lg" aria-labelledby="mdn-plus-button"><li class=" "><a href="/zh-CN/plus" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Overview</div><p class="submenu-item-description">A customized MDN experience</p></div></a></li><li class=" "><a href="/zh-CN/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li><li class=" "><a href="/zh-CN/plus/updates" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Updates</div><p class="submenu-item-description">All browser compatibility updates at a glance</p></div></a></li><li class=" "><a href="/en-US/plus/docs/features/overview" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Documentation</div><p class="submenu-item-description">Learn how to use MDN Plus</p></div></a></li><li class=" "><a href="/en-US/plus/docs/faq" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">FAQ</div><p class="submenu-item-description">Frequently asked questions about MDN Plus</p></div></a></li></ul></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/curriculum/">Curriculum <sup class="new">New</sup></a></li><li class="top-level-entry-container "><a class="top-level-entry menu-link" href="/en-US/blog/">Blog</a></li><li class="top-level-entry-container "><button type="button" id="tools-button" class="top-level-entry menu-toggle" aria-controls="tools-menu" aria-expanded="false">Tools</button><ul id="tools-menu" class="submenu tools hidden inline-submenu-lg" aria-labelledby="tools-button"><li class=" "><a href="/zh-CN/play" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">Playground</div><p class="submenu-item-description">Write, test and share your code</p></div></a></li><li class=" "><a href="/en-US/observatory" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">HTTP Observatory</div><p class="submenu-item-description">Scan a website for free</p></div></a></li><li class=" "><a href="/en-US/plus/ai-help" class="submenu-item "><div class="submenu-icon"></div><div class="submenu-content-container"><div class="submenu-item-heading">AI Help</div><p class="submenu-item-description">Get real-time assistance and support</p></div></a></li></ul></li></ul></nav><div class="header-search"><form action="/zh-CN/search" class="search-form search-widget" id="top-nav-search-form" role="search"><label id="top-nav-search-label" for="top-nav-search-input" class="visually-hidden">Search MDN</label><input aria-activedescendant="" aria-autocomplete="list" aria-controls="top-nav-search-menu" aria-expanded="false" aria-labelledby="top-nav-search-label" autoComplete="off" id="top-nav-search-input" role="combobox" type="search" class="search-input-field" name="q" placeholder="   " required="" value=""/><button type="button" class="button action has-icon clear-search-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear search input</span></span></button><button type="submit" class="button action has-icon search-button"><span class="button-wrap"><span class="icon icon-search "></span><span class="visually-hidden">Search</span></span></button><div id="top-nav-search-menu" role="listbox" aria-labelledby="top-nav-search-label"></div></form></div><div class="theme-switcher-menu"><button type="button" class="button action has-icon theme-switcher-menu small" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-theme-os-default "></span>Theme</span></button></div><ul class="auth-container"><li><a href="/users/fxa/login/authenticate/?next=%2Fzh-CN%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage" class="login-link" rel="nofollow">Log in</a></li><li><a href="/users/fxa/login/authenticate/?next=%2Fzh-CN%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage" target="_self" rel="nofollow" class="button primary mdn-plus-subscribe-link"><span class="button-wrap">Sign up for free</span></a></li></ul></div></div></header><div class="article-actions-container"><div class="container"><button type="button" class="button action has-icon sidebar-button" aria-label="Expand sidebar" aria-expanded="false" aria-controls="sidebar-quicklinks"><span class="button-wrap"><span class="icon icon-sidebar "></span></span></button><nav class="breadcrumbs-container" aria-label="Breadcrumb"><ol typeof="BreadcrumbList" vocab="https://schema.org/" aria-label="breadcrumbs"><li property="itemListElement" typeof="ListItem"><a href="/zh-CN/docs/Learn" class="breadcrumb" property="item" typeof="WebPage"><span property="name">学习 Web 开发</span></a><meta property="position" content="1"/></li><li property="itemListElement" typeof="ListItem"><a href="/zh-CN/docs/Learn/JavaScript" class="breadcrumb" property="item" typeof="WebPage"><span property="name">JavaScript——动态客户端脚本语言</span></a><meta property="position" content="2"/></li><li property="itemListElement" typeof="ListItem"><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs" class="breadcrumb" property="item" typeof="WebPage"><span property="name">客户端 Web API</span></a><meta property="position" content="3"/></li><li property="itemListElement" typeof="ListItem"><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="breadcrumb-current-page" property="item" typeof="WebPage"><span property="name">客户端存储</span></a><meta property="position" content="4"/></li></ol></nav><div class="article-actions"><button type="button" class="button action has-icon article-actions-toggle" aria-label="Article actions"><span class="button-wrap"><span class="icon icon-ellipses "></span><span class="article-actions-dialog-heading">Article Actions</span></span></button><ul class="article-actions-entries"><li class="article-actions-entry"><div class="languages-switcher-menu open-on-focus-within"><button id="languages-switcher-button" type="button" class="button action small has-icon languages-switcher-menu" aria-haspopup="menu"><span class="button-wrap"><span class="icon icon-language "></span>中文 (简体)</span></button><div class="hidden"><ul class="submenu language-menu " aria-labelledby="language-menu-button"><li class=" "><form class="submenu-item locale-redirect-setting"><div class="group"><label class="switch"><input type="checkbox" name="locale-redirect"/><span class="slider"></span><span class="label">Remember language</span></label><a href="https://github.com/orgs/mdn/discussions/739" rel="external noopener noreferrer" target="_blank" title="Enable this setting to automatically switch to this language when it&#x27;s available. (Click to learn more.)"><span class="icon icon-question-mark "></span></a></div></form></li><li class=" "><a data-locale="de" href="/de/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Deutsch</span><span title="Diese Übersetzung ist Teil eines Experiments."><span class="icon icon-experimental "></span></span></a></li><li class=" "><a data-locale="en-US" href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>English (US)</span></a></li><li class=" "><a data-locale="es" href="/es/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Español</span></a></li><li class=" "><a data-locale="fr" href="/fr/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Français</span></a></li><li class=" "><a data-locale="ja" href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>日本語</span></a></li><li class=" "><a data-locale="pt-BR" href="/pt-BR/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Português (do Brasil)</span></a></li><li class=" "><a data-locale="ru" href="/ru/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" class="button submenu-item"><span>Русский</span></a></li></ul></div></div></li></ul></div></div></div></div><div class="container"><div class="notecard localized-content-note"><p><a href="/zh-CN/docs/MDN/Community/Contributing/Translated_content#活跃语言">此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。</a></p></div></div><div class="main-wrapper"><div class="sidebar-container"><aside id="sidebar-quicklinks" class="sidebar" data-macro="LearnSidebar"><button type="button" class="button action backdrop" aria-label="Collapse sidebar"><span class="button-wrap"></span></button><nav aria-label="Related Topics" class="sidebar-inner"><header class="sidebar-actions"><section class="sidebar-filter-container"><div class="sidebar-filter "><label id="sidebar-filter-label" class="sidebar-filter-label" for="sidebar-filter-input"><span class="icon icon-filter"></span><span class="visually-hidden">Filter sidebar</span></label><input id="sidebar-filter-input" autoComplete="off" class="sidebar-filter-input-field false" type="text" placeholder="Filter" value=""/><button type="button" class="button action has-icon clear-sidebar-filter-button"><span class="button-wrap"><span class="icon icon-cancel "></span><span class="visually-hidden">Clear filter input</span></span></button></div></section></header><div class="sidebar-inner-nav"><div class="in-nav-toc"><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">在本文中</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#客户端存储?">客户端存储?</a></li><li class="document-toc-item "><a class="document-toc-link" href="#存储简单数据——web_存储">存储简单数据——Web 存储</a></li><li class="document-toc-item "><a class="document-toc-link" href="#存储复杂数据——indexeddb">存储复杂数据——IndexedDB</a></li><li class="document-toc-item "><a class="document-toc-link" href="#离线文件存储">离线文件存储</a></li><li class="document-toc-item "><a class="document-toc-link" href="#总结">总结</a></li><li class="document-toc-item "><a class="document-toc-link" href="#参见">参见</a></li></ul></section></div></div><div class="sidebar-body"><ol><li class="section"><a href="/zh-CN/docs/Learn/Getting_started_with_the_web">新手请从这开始!</a></li><li><details><summary>Web 入门</summary><ol><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web">Web 入门</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software">安装基础软件</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like">你的网站会是什么样子?</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files">处理文件</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics">HTML 基础</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics">CSS 基础</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript 基础</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website">发布你的网站</a></li><li><a href="/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works">万维网是如何工作的</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/HTML">HTML——构建 Web</a></li><li><details><summary>HTML 介绍</summary><ol><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML">HTML 简介</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started">开始学习 HTML</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">“头”里有什么——HTML 元信息</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals">HTML 文本处理基础</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks">创建超链接</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting">文本格式进阶</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure">文档与网站架构</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">HTML 调试</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter">标记信件</a></li><li><a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content">构建网页内容</a></li></ol></details></li><li><details><summary>多媒体与嵌入</summary><ol><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding">多媒体与嵌入</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML">HTML 中的图片</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">视频和音频内容</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies">从 object 到 iframe——其他嵌入技术</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web">向 web 中添加矢量图形</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">响应式图片</a></li><li><a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">Mozilla 欢迎页面</a></li></ol></details></li><li><details><summary>HTML 表格</summary><ol><li><a href="/zh-CN/docs/Learn/HTML/Tables">HTML 表格</a></li><li><a href="/zh-CN/docs/Learn/HTML/Tables/Basics">HTML 表格基础</a></li><li><a href="/zh-CN/docs/Learn/HTML/Tables/Advanced">HTML 表格进阶特性和无障碍</a></li><li><a href="/zh-CN/docs/Learn/HTML/Tables/Structuring_planet_data">作业:构建行星数据</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/CSS">CSS——设计 Web</a></li><li><details><summary>CSS 第一步</summary><ol><li><a href="/zh-CN/docs/Learn/CSS/First_steps">CSS 入门概述</a></li><li><a href="/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS">什么是 CSS?</a></li><li><a href="/zh-CN/docs/Learn/CSS/First_steps/Getting_started">让我们开始 CSS 的学习之旅</a></li><li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured">CSS 的组成</a></li><li><a href="/zh-CN/docs/Learn/CSS/First_steps/How_CSS_works">CSS 如何运行</a></li><li><a href="/zh-CN/docs/Learn/CSS/First_steps/Styling_a_biography_page">运用你的新知识</a></li></ol></details></li><li><details><summary>CSS 基础</summary><ol><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks">CSS 构建</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors">CSS 选择器</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors">类型、类和 ID 选择器</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors">属性选择器</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements">伪类和伪元素</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators">关系选择器</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">层叠、优先级与继承</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_layers">层叠层</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model">盒模型</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders">背景与边框</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions">处理不同方向的文本</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content">溢出的内容</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units">CSS 值和单位</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS">在 CSS 中调整大小</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements">图像、媒体和表单元素</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables">样式化表格</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects">高级区块效果</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS">调试 CSS</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Organizing">组织 CSS</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Fundamental_CSS_comprehension">基本的 CSS 理解</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper">创建精美的信纸</a></li><li><a href="/zh-CN/docs/Learn/CSS/Building_blocks/A_cool_looking_box">一个漂亮的盒子</a></li></ol></details></li><li><details><summary>样式化文本</summary><ol><li><a href="/zh-CN/docs/Learn/CSS/Styling_text">为文本添加样式(样式化文本)</a></li><li><a href="/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals">基本文本和字体样式</a></li><li><a href="/zh-CN/docs/Learn/CSS/Styling_text/Styling_lists">为列表添加样式</a></li><li><a href="/zh-CN/docs/Learn/CSS/Styling_text/Styling_links">样式化链接</a></li><li><a href="/zh-CN/docs/Learn/CSS/Styling_text/Web_fonts">Web 字体</a></li><li><a href="/zh-CN/docs/Learn/CSS/Styling_text/Typesetting_a_homepage">作业:排版社区大学首页</a></li></ol></details></li><li><details><summary>CSS 排版</summary><ol><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout">CSS 布局</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Introduction">介绍 CSS 布局</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Normal_Flow">常规流布局</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox">弹性盒子</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids">网格</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Floats">浮动</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Positioning">定位</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Multiple-column_Layout">多列布局</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Responsive_Design">响应式设计</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Media_queries">媒体查询入门指南</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods">传统的布局方法</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers">支持旧浏览器</a></li><li><a href="/zh-CN/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension">作业:基本布局理解</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/JavaScript">JavaScript——用户端动态脚本</a></li><li><details><summary>JavaScript 第一步</summary><ol><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps">JavaScript 第一步</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript">什么是 JavaScript?</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash">JavaScript 初体验</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong">查找并解决 JavaScript 代码的错误</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Variables">如何存储你需要的信息——变量</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Math">JavaScript 中的基础数学 — 数字和操作符</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Strings">文本处理——JavaScript 中的字符串</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods">有用的字符串方法</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Arrays">数组</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator">笑话生成器</a></li></ol></details></li><li><details><summary>JavaScript 基础</summary><ol><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">创建 JavaScript 代码块</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/conditionals">在代码中做决定——条件语句</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Looping_code">循环吧,代码</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Functions">函数——可复用的代码块</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Build_your_own_function">创建你自己的函数</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Return_values">函数返回值</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Events">事件介绍</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Event_bubbling">事件冒泡</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Building_blocks/Image_gallery">图片库</a></li></ol></details></li><li><details><summary>JavaScript 对象介绍</summary><ol><li><a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象入门</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">JavaScript 对象基础</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_programming">面向对象编程基本概念</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Classes_in_JavaScript">JavaScript 中的类</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/JSON">使用 JSON</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">实践对象构造</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">为“弹球”示例添加新功能</a></li></ol></details></li><li><details><summary>异步 JavaScript</summary><ol><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous">异步 JavaScript</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing">异步 JavaScript 简介</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises">如何使用 Promise</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Implementing_a_promise-based_API">如何实现基于 Promise 的 API</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing_workers">workers 简介</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Asynchronous/Sequencing_animations">序列动画</a></li></ol></details></li><li><details open=""><summary>客户端 Web API</summary><ol><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs">客户端 Web API</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API 简介</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">操作文档</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">从服务器获取数据</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">第三方 API</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">绘图</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">视频和音频 API</a></li><li><em><a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage" aria-current="page">客户端存储</a></em></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Forms">Web 表单——与用户数据打交道</a></li><li><details><summary>Web 表单核心</summary><ol><li><a href="/zh-CN/docs/Learn/Forms">Web 表单构建块</a></li><li><a href="/zh-CN/docs/Learn/Forms/Your_first_form">创建我的第一个表单</a></li><li><a href="/zh-CN/docs/Learn/Forms/How_to_structure_a_web_form">如何构建 HTML 表单</a></li><li><a href="/zh-CN/docs/Learn/Forms/Basic_native_form_controls">原生表单部件</a></li><li><a href="/zh-CN/docs/Learn/Forms/HTML5_input_types">HTML5 的输入(input)类型</a></li><li><a href="/zh-CN/docs/Learn/Forms/Other_form_controls">其他表单控件</a></li><li><a href="/zh-CN/docs/Learn/Forms/Styling_web_forms">样式化 Web 表单</a></li><li><a href="/zh-CN/docs/Learn/Forms/Advanced_form_styling">表单样式化进阶</a></li><li><a href="/zh-CN/docs/Learn/Forms/UI_pseudo-classes">UI 伪类</a></li><li><a href="/zh-CN/docs/Learn/Forms/Form_validation">表单数据校验</a></li><li><a href="/zh-CN/docs/Learn/Forms/Sending_and_retrieving_form_data">发送表单数据</a></li></ol></details></li><li><details><summary>Web 表单进阶</summary><ol><li><a href="/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls">如何构建自定义表单控件</a></li><li><a href="/zh-CN/docs/Learn/Forms/Sending_forms_through_JavaScript">使用 JavaScript 发送表单</a></li><li><a href="/zh-CN/docs/Learn/Forms/Property_compatibility_table_for_form_controls">表单控件兼容性列表</a></li><li><a href="/zh-CN/docs/Learn/Forms/HTML_forms_in_legacy_browsers">旧式浏览器中的 HTML 表单</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Accessibility">无障碍——使每个人都能使用 Web</a></li><li><details><summary>无障碍指南</summary><ol><li><a href="/zh-CN/docs/Learn/Accessibility">无障碍</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/What_is_accessibility">什么是无障碍?</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/HTML">HTML:无障碍的良好基础</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/CSS_and_JavaScript">CSS 和 JavaScript 无障碍最佳实践</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA 基础</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/Multimedia">多媒体无障碍</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/Mobile">移动端无障碍</a></li><li><a href="/zh-CN/docs/Learn/Accessibility/Accessibility_troubleshooting">测验:无障碍疑难解答</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Performance">性能——使网站快速响应</a></li><li><details><summary>性能指南</summary><ol><li><a href="/zh-CN/docs/Learn/Performance">Web 性能</a></li><li><a href="/zh-CN/docs/Learn/Performance/why_web_performance">Web 性能的重要性</a></li><li><a href="/zh-CN/docs/Learn/Performance/What_is_web_performance">什么是 web 性能?</a></li><li><a href="/zh-CN/docs/Learn/Performance/Perceived_performance">感知性能</a></li><li><a href="/zh-CN/docs/Learn/Performance/Measuring_performance">测量性能</a></li><li><a href="/zh-CN/docs/Learn/Performance/Multimedia">多媒体:图片</a></li><li><a href="/zh-CN/docs/Learn/Performance/video">多媒体:视频</a></li><li><a href="/zh-CN/docs/Learn/Performance/JavaScript">JavaScript 性能优化</a></li><li><a href="/zh-CN/docs/Learn/Performance/HTML">HTML 性能优化</a></li><li><a href="/zh-CN/docs/Learn/Performance/CSS">CSS 性能优化</a></li><li><a href="/zh-CN/docs/Learn/Performance/business_case_for_performance">web 性能的商业案例</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/MathML">MathML——使用 MathML 语言撰写数学表达式</a></li><li><details><summary>MathML 第一步</summary><ol><li><a href="/zh-CN/docs/Learn/MathML/First_steps">MathML 入门概述</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Getting_started">MathML 使用入门</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Text_containers">MathML 文本容器</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Fractions_and_roots">MathML 分数和根号</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Scripts">MathML 附加符号</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Tables">MathML 表格</a></li><li><a href="/zh-CN/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas">三个著名的数学公式</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/../Games">游戏——开发 Web 游戏</a></li><li><details><summary>指南和基础教程</summary><ol><li><a href="/zh-CN/docs/Games/Introduction">Web 游戏开发简介</a></li><li><a href="/zh-CN/docs/Games/Techniques">游戏开发技术</a></li><li><a href="/zh-CN/docs/Games/Tutorials">教程</a></li><li><a href="/zh-CN/docs/Games/Publishing_games">发布游戏</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Tools_and_testing">工具与测试</a></li><li><details><summary>客户端 web 开发工具</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools">理解客户端 web 开发工具</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">客户端工具概述</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">命令行速成课</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">软件包管理基础</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">介绍完整的工具链</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">部署我们的应用</a></li></ol></details></li><li><details><summary>客户端框架介绍</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">客户端框架介绍</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">框架的主要特性</a></li></ol></details></li><li><details><summary>React</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">React 入门</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">开始我们的 React 待办清单</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">组件化我们的 React App</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li></ol></details></li><li><details><summary>Ember</summary><ol><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li></ol></details></li><li><details><summary>Vue</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">开始使用 Vue</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">创建第一个 Vue 组件</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">渲染 Vue 组件列表</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">使用 Vue event、method 和 model 添加一个新的 todo 表单</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">使用 CSS 为 Vue 组件添加样式</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Vue 中的计算属性</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue 中的条件渲染:编辑现有的待办事项</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">使用 Vue 模板引用进行焦点管理</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue 资源</a></li></ol></details></li><li><details><summary>Svelte</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Svelte 入门</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">开始编写我们的 Svelte 待办事项列表应用程序</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Svelte 中的动态行为:变量和属性</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">将我们的 Svelte 应用组件化</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Svelte 进阶:响应式、生命周期以及无障碍</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">使用 Svelte store</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">Svelte 对 TypeScript 的支持</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">部署以及下一步</a></li></ol></details></li><li><details><summary>Angular</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_getting_started">Angular 入门</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_todo_list_beginning">Beginning our Angular todo list app</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_styling">Styling our Angular app</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_item_component">Creating an item component</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_filtering">Filtering our to-do items</a></li><li><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_building">Building Angular applications and further resources</a></li></ol></details></li><li><details><summary>Git 和 GitHub</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/GitHub">Git 和 GitHub</a></li></ol></details></li><li><details><summary>跨浏览器测试</summary><ol><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing">跨浏览器测试</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试介绍</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试的策略</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的 HTML 和 CSS 问题</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的 JavaScript 问题</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">解决常见的无障碍问题</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特性检测</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动化测试简介</a></li><li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">搭建自己的自动化测试环境</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Server-side">服务端网页编程</a></li><li><details><summary>第一步</summary><ol><li><a href="/zh-CN/docs/Learn/Server-side/First_steps">服务端网站编程的第一步</a></li><li><a href="/zh-CN/docs/Learn/Server-side/First_steps/Introduction">服务端编程介绍</a></li><li><a href="/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview">客户端服务端交互概述</a></li><li><a href="/zh-CN/docs/Learn/Server-side/First_steps/Web_frameworks">服务端 web 框架</a></li><li><a href="/zh-CN/docs/Learn/Server-side/First_steps/Website_security">站点安全</a></li></ol></details></li><li><details><summary>Django Web 框架(Python)</summary><ol><li><a href="/zh-CN/docs/Learn/Server-side/Django">Django Web 框架 (python)</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Introduction">Django 介绍</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/development_environment">设置 Django 开发环境</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: 创建网站的地基</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: 使用模型</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django 管理员站点</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Home_page">Django 教程 5:主页构建</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Generic_views">Django 教程 6: 通用列表和详细信息视图</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Sessions">Django 教程 7: 会话框架</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Authentication">Django 教程 8:用户授权与许可</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Forms">Django 教程 9: 使用表单</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Testing">Django 教程 10: 测试 Django 网页应用</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/Deployment">Django 教程 11:部署 Django 到生产环境</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/web_application_security">Django Web 应用安全</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog">评估:DIY Django 微博客</a></li></ol></details></li><li><details><summary>Express Web 框架(Node.js/JavaScript)</summary><ol><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs">Express Web 框架(Node.js/JavaScript)</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node 开发环境</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库 (Mongoose)</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li><li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li></ol></details></li><li class="section"><a href="/zh-CN/docs/Learn/Common_questions">更多资源</a></li><li><details><summary>常见问题</summary><ol><li><a href="/zh-CN/docs/Learn/Common_questions">常见问题</a></li><li><a href="/zh-CN/docs/Learn/HTML/Howto">使用 HTML 解决常见问题</a></li><li><a href="/zh-CN/docs/Learn/CSS/Howto">解决常见的 CSS 问题</a></li><li><a href="/zh-CN/docs/Learn/JavaScript/Howto">解决 JavaSctript 代码的常见问题</a></li><li><a href="/zh-CN/docs/Learn/Common_questions/Web_mechanics">Web 机制</a></li><li><a href="/zh-CN/docs/Learn/Common_questions/Tools_and_setup">工具和安装</a></li><li><a href="/zh-CN/docs/Learn/Common_questions/Design_and_accessibility">设计与无障碍</a></li></ol></details></li></ol></div></div><section class="place side"></section></nav></aside><div class="toc-container"><aside class="toc"><nav><div class="document-toc-container"><section class="document-toc"><header><h2 class="document-toc-heading">在本文中</h2></header><ul class="document-toc-list"><li class="document-toc-item "><a class="document-toc-link" href="#客户端存储?">客户端存储?</a></li><li class="document-toc-item "><a class="document-toc-link" href="#存储简单数据——web_存储">存储简单数据——Web 存储</a></li><li class="document-toc-item "><a class="document-toc-link" href="#存储复杂数据——indexeddb">存储复杂数据——IndexedDB</a></li><li class="document-toc-item "><a class="document-toc-link" href="#离线文件存储">离线文件存储</a></li><li class="document-toc-item "><a class="document-toc-link" href="#总结">总结</a></li><li class="document-toc-item "><a class="document-toc-link" href="#参见">参见</a></li></ul></section></div></nav></aside><section class="place side"></section></div></div><main id="content" class="main-content "><article class="main-page-content" lang="zh-CN"><header><h1>客户端存储</h1></header><div class="section-content"><ul class="prev-next"> <li><a class="button secondary" href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs"><span class="button-wrap"> 上一页 </span></a></li> <li><a class="button secondary" href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs"><span class="button-wrap"> 概述:客户端 Web API</span></a></li> </ul> <p>现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。</p> <figure class="table-container"><table class="learn-box standard-table"> <tbody> <tr> <th scope="row">前提:</th> <td>JavaScript 基础(参见<a href="/zh-CN/docs/Learn/JavaScript/First_steps">第一步</a>、<a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">构建代码块</a>、<a href="/zh-CN/docs/Learn/JavaScript/Objects">JavaScript 对象</a>)、<a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">基础的客户端 API</a></td> </tr> <tr> <th scope="row">目标:</th> <td>学习如何使用客户端存储 API 来存储应用数据。</td> </tr> </tbody> </table></figure></div><section aria-labelledby="客户端存储?"><h2 id="客户端存储?"><a href="#客户端存储?">客户端存储?</a></h2><div class="section-content"><p>在其他的 MDN 学习中我们已经讨论过<a href="/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99">静态网站</a>和<a href="/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#%E5%8A%A8%E6%80%81%E7%BD%91%E7%AB%99">动态网站</a>的区别。大多数现代的网站是动态的——它们在服务端使用各种类型的数据库(服务端存储)来存储数据,之后通过运行<a href="/zh-CN/docs/Learn/Server-side">服务端</a>代码来查询需要的数据,把其数据插入到静态页面的模板中,并将生成的 HTML 提供给客户端,以在用户的浏览器中显示。</p> <p>客户端存储以相同的原理工作,但是在使用上有一些不同。它是由 JavaScript API 组成的因此允许你在客户端存储数据(比如在用户的机器上),而且可以在需要的时候查询相关的数据。这有很多明显的用处,比如:</p> <ul> <li>个性化网站偏好(比如显示一个用户选择的自定义微件、颜色主题或字体大小)。</li> <li>保存之前的站点行为(比如从先前的 session 中获取购物车中的内容,记住用户是否之前已经登陆过)。</li> <li>本地化保存数据和静态资源可以使一个站点更快(至少让资源变少)的下载,甚至可以在失去网络连接的情况下可用。</li> <li>将 Web 应用生成的文档保存在本地以供离线使用。</li> </ul> <p>通常客户端和服务端存储是结合在一起使用的。例如,你可以从数据库中下载一个由 Web 游戏或音乐播放器应用程序使用的音乐文件,将它们存储在客户端数据库中,并按需要播放它们。用户只需下载音乐文件一次——在随后的访问中,它们将从数据库中检索。</p> <div class="notecard note"> <p><strong>备注:</strong>使用客户端存储 API 可以存储的数据量是有限的(可能是每个 API 单独的和累积的总量);具体的限制取决于浏览器,也可能基于用户设置。参见<a href="/zh-CN/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria">浏览器存储限制和清理标准</a>以了解更多信息。</p> </div></div></section><section aria-labelledby="老做派:cookie"><h3 id="老做派:cookie"><a href="#老做派:cookie">老做派:cookie</a></h3><div class="section-content"><p>客户端存储的概念已经存在很长一段时间了。从 Web 的早期时代开始,网站就使用 <a href="/zh-CN/docs/Web/HTTP/Cookies">cookie</a> 来存储信息,以在网站上提供个性化的用户体验。它们是 Web 中最早、最常用的客户端存储形式。</p> <p>如今,有更简单的机制可用于存储客户端数据,因此我们在本文中不会教授如何使用 cookie。然而,这并不意味着 cookie 在现代 Web 上完全没有用处——它们仍然被广泛用于存储与用户个性化和状态相关的数据,例如会话 ID 和访问令牌。有关 cookie 的更多信息,请参见我们的<a href="/zh-CN/docs/Web/HTTP/Cookies">使用 HTTP cookie</a> 文章。</p></div></section><section aria-labelledby="新流派:web_存储和_indexeddb"><h3 id="新流派:web_存储和_indexeddb"><a href="#新流派:web_存储和_indexeddb">新流派:Web 存储和 IndexedDB</a></h3><div class="section-content"><p>我们在上面所提到的“更简单”的特性如下:</p> <ul> <li><a href="/zh-CN/docs/Web/API/Web_Storage_API">Web 存储 API</a> 提供了一种非常简单的语法,用于存储和检索较小的、由名称和相应值组成的数据项。当你只需要存储一些简单的数据时,比如用户的名字、用户是否登录、屏幕背景使用了什么颜色等等,这是非常有用的。</li> <li><a href="/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB API</a> 为浏览器提供了一个完整的数据库系统来存储复杂的数据。这可以用于存储从完整的用户记录到甚至是复杂的数据类型,如音频或视频文件。</li> </ul> <p>你将在下面了解更多关于这些 API 的信息。</p></div></section><section aria-labelledby="cache_api"><h3 id="cache_api"><a href="#cache_api">Cache API</a></h3><div class="section-content"><p><a href="/zh-CN/docs/Web/API/Cache"><code>Cache</code></a> API 是为存储特定 HTTP 请求的响应文件而设计的,它对于像存储离线网站文件这样的事情非常有用,这样网站就可以在没有网络连接的情况下使用。缓存通常与 <a href="/zh-CN/docs/Web/API/Service_Worker_API">Service Worker API</a> 组合使用,尽管不一定非要这么做。</p> <p>Cache 和 Service Worker 的使用是一个高级主题,我们不会在本文中详细讨论它,尽管我们将在下面的<a href="#%E7%A6%BB%E7%BA%BF%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8">离线文件存储</a>小节中展示一个简单的例子。</p></div></section><section aria-labelledby="存储简单数据——web_存储"><h2 id="存储简单数据——web_存储"><a href="#存储简单数据——web_存储">存储简单数据——Web 存储</a></h2><div class="section-content"><p><a href="/zh-CN/docs/Web/API/Web_Storage_API">Web 存储 API</a> 非常容易使用——你只需存储简单的键名/键值对数据(限制为字符串、数字等类型)并在需要的时候检索其值。</p></div></section><section aria-labelledby="基本语法"><h3 id="基本语法"><a href="#基本语法">基本语法</a></h3><div class="section-content"><p>让我们来告诉你怎么做:</p> <ol> <li> <p>第一步,访问 GitHub 上的 <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html" class="external" target="_blank">Web 存储空白模板</a>(在新标签页打开它)。</p> </li> <li> <p>打开你浏览器开发者工具的 JavaScript 控制台。</p> </li> <li> <p>你所有的 Web 存储数据都包含在浏览器内两个类似于对象的结构中:<a href="/zh-CN/docs/Web/API/Window/sessionStorage" title="sessionStorage"><code>sessionStorage</code></a> 和 <a href="/zh-CN/docs/Web/API/Window/localStorage" title="localStorage"><code>localStorage</code></a>。第一种方法,只要浏览器开着,数据就会一直保存(关闭浏览器时数据会丢失),而第二种会一直保存数据,甚至到浏览器关闭又开启后也是这样。我们将在本文中使用第二种方法,因为它通常更有用。</p> <p><a href="/zh-CN/docs/Web/API/Storage/setItem"><code>Storage.setItem()</code></a> 方法允许你在存储中保存一个数据项——它接受两个参数:数据项的名字及其值。试着把它输入到你的 JavaScript 控制台(如果你愿意的话,可以把它的值改为你自己的名字!)</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.setItem("name", "Chris"); </code></pre></div> </li> <li> <p><a href="/zh-CN/docs/Web/API/Storage/getItem"><code>Storage.getItem()</code></a> 方法接受一个参数——你想要检索的数据项的名称——并返回数据项的值。现在将这些代码输入到你的 JavaScript 控制台:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>在输入第二行时,你应该会看到 <code>myName</code> 变量现在包含 <code>name</code> 数据项的值。</p> </li> <li> <p><a href="/zh-CN/docs/Web/API/Storage/removeItem"><code>Storage.removeItem()</code></a> 方法接受一个参数——你想要删除的数据项的名称——并从 Web 存储中删除该数据项。在你的 JavaScript 控制台中输入以下几行:</p> </li> </ol> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.removeItem("name"); myName = localStorage.getItem("name"); myName; </code></pre></div> <p>第三行现在应该返回 <code>null</code>——<code>name</code> 项已经不存在于 Web 存储中。</p></div></section><section aria-labelledby="数据会一直存在!"><h3 id="数据会一直存在!"><a href="#数据会一直存在!">数据会一直存在!</a></h3><div class="section-content"><p>Web 存储的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对 <code>localStorage</code> 而言)。让我们来看看这个:</p> <ol> <li> <p>再次打开我们的 Web 存储空白模板,但是这次你要在不同的浏览器中打开这个教程!这样可以更容易处理。</p> </li> <li> <p>在浏览器的 JavaScript 控制台中输入这几行:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>localStorage.setItem("name", "Chris"); let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>你应该看到 name 数据项返回。</p> </li> <li> <p>现在关掉浏览器再把它打开。</p> </li> <li> <p>再次输入下面几行:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>let myName = localStorage.getItem("name"); myName; </code></pre></div> <p>你应该看到,尽管浏览器已经关闭,然后再次打开,但仍然可以使用该值。</p> </li> </ol></div></section><section aria-labelledby="为每个域名分离储存"><h3 id="为每个域名分离储存"><a href="#为每个域名分离储存">为每个域名分离储存</a></h3><div class="section-content"><p>每个域都有一个单独的数据存储区(每个单独的网址都在浏览器中加载)。你会看到,如果你加载两个网站(例如 google.com 和 amazon.com)并尝试将某个项目存储在一个网站上,该数据项将无法从另一个网站获取。</p> <p>这是有道理的——你可以想象如果网站能够查看彼此的数据,就会出现安全问题!</p></div></section><section aria-labelledby="更复杂的例子"><h3 id="更复杂的例子"><a href="#更复杂的例子">更复杂的例子</a></h3><div class="section-content"><p>让我们通过编写一个简单的工作示例来应用这些新发现的知识,让你了解如何使用网络存储。我们的示例将允许你输入一个名称,然后该页面将刷新,以提供个性化问候。这种状态也会页面/浏览器重新加载期间保持,因为这个名称存储在 Web 存储中。</p> <p>你可以在 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">personal-greeting.html</a> 中找到示例 HTML——这包含一个具有标题、内容和页脚,以及用于输入你的姓名的表单的简单网站。</p> <p> <img src="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/web-storage-demo.png" alt="一张网站的截图,包含了页头、内容和页脚部分。页头的左侧有一段欢迎文本,右侧有一个标记为“忘记”的按钮。内容部分包括一个标题,接着是两段占位文本。页脚显示“版权归任何人所有。随意使用代码。”" width="1024" height="550" loading="lazy"> </p> <p>让我们来构建示例,以便了解它的工作原理。</p> <ol> <li> <p>首先,在你的计算机上的新目录中创建一个 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">personal-greeting.html</a> 文件的副本。</p> </li> <li> <p>接下来,请注意我们的 HTML 如何引用一个名为 <code>index.js</code> 的 JavaScript 文件(就像 <code>&lt;script src="index.js" defer&gt;&lt;/script&gt;</code>)。我们需要创建它并将 JavaScript 代码写入其中。在与 HTML 文件相同的目录中创建一个 <code>index.js</code> 文件。</p> </li> <li> <p>我们首先创建对所有需要在此示例中操作的 HTML 特性的引用——我们将它们全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。将以下几行添加到你的 JavaScript 文件中:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 创建所需的常量 const rememberDiv = document.querySelector(".remember"); const forgetDiv = document.querySelector(".forget"); const form = document.querySelector("form"); const nameInput = document.querySelector("#entername"); const submitBtn = document.querySelector("#submitname"); const forgetBtn = document.querySelector("#forgetname"); const h1 = document.querySelector("h1"); const personalGreeting = document.querySelector(".personal-greeting"); </code></pre></div> </li> <li> <p>接下来,我们需要包含一个小小的事件监听器,以在按下提交按钮时阻止实际的提交表单动作自身,因为这不是我们想要的行为。在你之前的代码下添加此代码段:在你之前的代码后添加这段代码:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 当按钮按下时阻止表单提交 form.addEventListener("submit", (e) =&gt; e.preventDefault()); </code></pre></div> </li> <li> <p>现在我们需要添加一个事件监听器,当单击“Say hello”按钮时,它的处理函数将会运行。这些注释详细解释了每一处都做了什么,但实际上我们在这里获取用户输入到文本输入框中的名字并使用 <code>setItem()</code> 将它保存在网络存储中,然后运行一个名为 <code>nameDisplayCheck()</code> 的函数来处理实际的网站文本的更新。将此添加到代码的底部:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 当点击“Say hello”按钮时运行函数 submitBtn.addEventListener("click", () =&gt; { // 将输入的名字存储到网页存储中 localStorage.setItem("name", nameInput.value); // 运行 nameDisplayCheck() 来处理显示个性化问候语和更新表单显示 nameDisplayCheck(); }); </code></pre></div> </li> <li> <p>此时,我们还需要一个事件处理器,以便在单击“Forget”按钮时运行一个函数——且仅在单击“Say hello”按钮(两种表单状态来回切换)后才显示。在这个函数中,我们使用 <code>removeItem()</code> 从 Web 存储中删除项目 <code>name</code>,然后再次运行 <code>nameDisplayCheck()</code> 以更新显示。将其添加到底部:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 当点击“Forget”按钮时运行函数 forgetBtn.addEventListener("click", () =&gt; { // 从网页存储中移除存储的名字 localStorage.removeItem("name"); // 运行 nameDisplayCheck() 来重新显示通用问候语并更新表单显示 nameDisplayCheck(); }); </code></pre></div> </li> <li> <p>现在是时候定义 <code>nameDisplayCheck()</code> 函数本身了。在这里,我们通过使用 <code>localStorage.getItem('name')</code> 作为测试条件来检查 name 数据项是否已经存储在 Web 存储中。如果它已被存储,则该调用的返回值为 <code>true</code>;果没有,它会是 <code>false</code>。如果是 <code>true</code>,我们会显示个性化问候语,显示表格的“forget”部分,并隐藏表格的“Say hello”部分。如果是 <code>false</code>,我们会显示一个通用问候语,并做相反的事。再次将下面的代码添到底部:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 nameDisplayCheck() 函数 function nameDisplayCheck() { // 检查 'name' 数据项是否存储在网页存储中 if (localStorage.getItem("name")) { // 如果存在,显示个性化问候语 const name = localStorage.getItem("name"); h1.textContent = `欢迎,${name}`; personalGreeting.textContent = `欢迎来到我们的网站,${name}!希望您在这里玩得开心。`; // 隐藏表单中的 'remember' 部分,显示 'forget' 部分 forgetDiv.style.display = "block"; rememberDiv.style.display = "none"; } else { // 如果不存在,显示通用问候语 h1.textContent = "欢迎来到我们的网站"; personalGreeting.textContent = "欢迎来到我们的网站。希望您在这里玩得开心。"; // 隐藏表单中的 'forget' 部分,显示 'remember' 部分 forgetDiv.style.display = "none"; rememberDiv.style.display = "block"; } } </code></pre></div> </li> <li> <p>最后但同样重要的是,我们需要在每次加载页面时运行 <code>nameDisplayCheck()</code> 函数。如果我们不这样做,那么个性化问候不会在页面重新加载后保持。将以下代码添加到代码的底部:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>nameDisplayCheck(); </code></pre></div> </li> </ol> <p>你的例子完成了——做得好!现在剩下的就是保存你的代码并在浏览器中测试你的 HTML 页面。你可以在这里看到我们的<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html" class="external" target="_blank">完成版本并在线运行</a>。</p> <div class="notecard note"> <p><strong>备注:</strong>在<a href="/zh-CN/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">使用 Web 存储 API</a> 中还有一个稍微复杂点儿的示例。</p> </div> <div class="notecard note"> <p><strong>备注:</strong>在完成版本的源代码中,<code>&lt;script src="index.js" defer&gt;&lt;/script&gt;</code> 一行里,<code>defer</code> 属性指明在页面加载完成之前,<a href="/zh-CN/docs/Web/HTML/Element/script"><code>&lt;script&gt;</code></a> 元素的内容不会执行。</p> </div></div></section><section aria-labelledby="存储复杂数据——indexeddb"><h2 id="存储复杂数据——indexeddb"><a href="#存储复杂数据——indexeddb">存储复杂数据——IndexedDB</a></h2><div class="section-content"><p><a href="/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB API</a>(有时简称 IDB)是可以在浏览器中访问的一个完整的数据库系统,在这里,你可以存储复杂的关系数据。其种类不限于像字符串和数字这样的简单值。你可以在一个 IndexedDB 中存储视频,图像和许多其他的内容。</p> <p>IndexedDB API 允许你创建一个数据库,然后在该数据库中创建对象存储。对象存储类似于关系型数据库中的表,每个对象存储可以包含多个对象。要了解有关 IndexedDB API 的更多信息,请参见<a href="/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB">使用 IndexedDB</a>。</p> <p>但是,这确实是有代价的:使用 IndexedDB 要比 Web 存储 API 复杂得多。在本节中,我们仅仅只能浅尝辄止地一提它的能力,不过我们会给你足够基础知识以帮助你开始。</p></div></section><section aria-labelledby="通过一个笔记存储示例演示"><h3 id="通过一个笔记存储示例演示"><a href="#通过一个笔记存储示例演示">通过一个笔记存储示例演示</a></h3><div class="section-content"><p>在这里,我们将向你介绍一个示例,该示例允许你在浏览器中存储笔记并随时查看和删除它们,在我们进行时,我们将解释 IDB 的最基本部分并让你自己构建笔记。</p> <p>这个应用看起来像这样:</p> <p> <img src="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/idb-demo.png" alt="IndexDB 笔记演示的截图包含四个部分。第一部分是页头。第二部分列出了所有已创建的笔记,包括两条笔记,每条笔记都有一个删除按钮。第三部分是一个表单,包含两个输入字段用于“笔记标题”和“笔记内容”,以及一个标记为“创建新笔记”的按钮。底部部分的页脚显示“版权归任何人所有。随意使用代码。”" width="803" height="736" loading="lazy"> </p> <p>每个笔记都有一个标题和一些正文,每个都可以单独编辑。我们将在下面通过的 JavaScript 代码提供详细的笔记,以帮助你了解正在发生的事情。</p></div></section><section aria-labelledby="开始"><h3 id="开始"><a href="#开始">开始</a></h3><div class="section-content"><p>1、首先,将 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html" class="external" target="_blank"><code>index.html</code></a>、<a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css" class="external" target="_blank"><code>style.css</code></a> 和 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js" class="external" target="_blank"><code>index-start.js</code></a> 文件的本地副本放入本地计算机上的新目录中。</p> <p>2、浏览这些文件。你将看到 HTML 非常简单:具有页眉和页脚的网站,以及包含显示笔记的位置的主内容区域,以及用于在数据库中输入新笔记的表单。CSS 提供了一些简单的样式,使其更清晰。JavaScript 文件包含五个声明的常量,其中包含对将显示笔记的 <a href="/zh-CN/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> 元素的引用、标题和正文 <a href="/zh-CN/docs/Web/HTML/Element/input"><code>&lt;input&gt;</code></a> 元素、<a href="/zh-CN/docs/Web/HTML/Element/form"><code>&lt;form&gt;</code></a> 本身,以及 <a href="/zh-CN/docs/Web/HTML/Element/button"><code>&lt;button&gt;</code></a>。</p> <p>3、将你的 JavaScript 文件重命名为 <code>index.js</code>。你现在可以开始向其添加代码了。</p></div></section><section aria-labelledby="数据库初始设置"><h3 id="数据库初始设置"><a href="#数据库初始设置">数据库初始设置</a></h3><div class="section-content"><p>现在让我们来看看为了建立数据库必须首先要做什么。</p> <ol> <li> <p>在常量声明下,加入这几行:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 创建一个 db 对象的实例,用于存储打开的数据库 let db; </code></pre></div> <p>这里我们声明了一个叫 <code>db</code> 的变量——这将在之后被用来存储一个代表数据库的对象。我们将在几个地方使用它,所以我们为了方便使用而在这里把它声明为全局的。</p> </li> <li> <p>接着,添加如下代码:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 打开我们的数据库;如果数据库不存在,将会创建它 // (请参见下面的 upgradeneeded 处理器) const openRequest = window.indexedDB.open("notes_db", 1); </code></pre></div> <p>这一行代码创建了一个请求,用于打开名为 <code>notes_db</code> 的版本 <code>1</code> 的数据库。如果该数据库尚不存在,它将由后续代码创建。你会在 IndexedDB 中经常看到这种请求模式。数据库操作需要时间。你不希望在等待结果时使浏览器卡死,因此数据库操作是<a href="/zh-CN/docs/Glossary/Asynchronous">异步</a>的,意味着操作不会立即发生,而是在未来的某个时间发生,并且你会在操作完成时收到通知。</p> <p>在 IndexedDB 中处理这一点的方法是创建一个请求对象(可以随意命名——我们在这里称之为 <code>openRequest</code>,这样它的用途就很明显)。然后,你可以使用事件处理器来运行代码,当请求完成、失败等时,你可以看到下面的用法。</p> <div class="notecard note"> <p><strong>备注:</strong>版本号很重要。如果你想升级数据库(例如,通过更改表结构),你需要再次运行代码,增加版本号,并在 <code>upgradeneeded</code> 处理器中指定不同的模式等。我们在本教程中不会涉及数据库的升级。</p> </div> </li> <li> <p>现在,在你之前添加的代码下面添加以下事件处理器:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 错误处理器表示数据库未成功打开 openRequest.addEventListener("error", () =&gt; console.error("数据库打开失败")); // 成功处理器表示数据库成功打开 openRequest.addEventListener("success", () =&gt; { console.log("数据库成功打开"); // 将打开的数据库对象存储在 db 变量中。下面会多次使用 db = openRequest.result; // 运行 displayData() 函数以显示已存在于 IDB 中的笔记 displayData(); }); </code></pre></div> <p><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBRequest/error_event"><code>error</code></a> 事件处理器会在系统返回请求失败的消息时运行。这允许你对这个问题做出响应。在我们的示例中,我们只是将一条消息打印到 JavaScript 控制台。</p> <p><a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBRequest/success_event"><code>success</code></a> 事件处理器会在请求成功返回时运行,意味着数据库已成功打开。如果是这种情况,表示打开的数据库的对象会在 <a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBRequest/result"><code>openRequest.result</code></a> 属性中提供,允许我们操作数据库。我们将其存储在之前创建的 <code>db</code> 变量中以供后续使用。我们还会运行一个名为 <code>displayData()</code> 的函数,用于在 HTML 中的 <code>ul</code> 元素内显示数据库中的数据。我们现在运行它,以便在页面加载时立即显示数据库中已经存在的笔记。你会在稍后看到 <code>displayData()</code> 的定义。</p> </li> <li> <p>最后,为了完成这一部分,我们将添加可能是设置数据库时最重要的事件处理器:<a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event"><code>upgradeneeded</code></a>。如果数据库尚未设置,或数据库以比现有存储的数据库更大的版本号打开(进行升级时),该处理器会运行。在你之前的处理器下面添加以下代码:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 如果尚未设置数据库表,则进行设置 openRequest.addEventListener("upgradeneeded", (e) =&gt; { // 获取已打开的数据库的引用 db = e.target.result; // 在我们的数据库中创建一个用于存储笔记和自增键的 objectStore // objectStore 类似于关系数据库中的“表” const objectStore = db.createObjectStore("notes_os", { keyPath: "id", autoIncrement: true, }); // 定义 objectStore 将包含的数据项 objectStore.createIndex("title", "title", { unique: false }); objectStore.createIndex("body", "body", { unique: false }); console.log("数据库设置完成"); }); </code></pre></div> <p>在这里我们定义了数据库的模式(结构);即它包含的列(或字段)集合。首先,我们从事件的目标 (<code>e.target.result</code>) 的 <code>result</code> 属性中获取现有数据库的引用,这就是 <code>request</code> 对象。这等同于在<code>成功</code>事件处理器中的 <code>db = openRequest.result;</code>,但我们需要在这里单独进行,因为 <code>upgradeneeded</code> 事件处理器(如果需要的话)会在<code>成功</code>事件处理器之前运行,这意味着如果我们不这样做,<code>db</code> 值将不可用。</p> <p>然后,我们使用 <a href="/zh-CN/docs/Web/API/IDBDatabase/createObjectStore"><code>IDBDatabase.createObjectStore()</code></a> 在打开的数据库中创建一个名为 <code>notes_os</code> 的新 objectStore。这相当于传统数据库系统中的一个表。我们给它指定了名称 <code>notes</code>,并指定了一个 <code>autoIncrement</code> 键字段 <code>id</code>——在每条新记录中,这个字段会自动分配递增的值——开发者不需要显式设置它。作为键,<code>id</code> 字段将用于唯一标识记录,例如在删除或显示记录时。</p> <p>我们还使用 <a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBObjectStore/createIndex"><code>IDBObjectStore.createIndex()</code></a> 方法创建了两个其他索引(字段):<code>title</code>(包含每条笔记的标题)和 <code>body</code>(包含笔记的正文内容)。</p> <p>设置好这个数据库模式后,当我们开始向数据库中添加记录时,每条记录将表示为类似于以下格式的对象:</p> <div class="code-example"><div class="example-header"><span class="language-name">json</span></div><pre class="brush: json notranslate"><code>{ "title": "Buy milk", "body": "Need both cows milk and soy.", "id": 8 } </code></pre></div> </li> </ol></div></section><section aria-labelledby="添加数据到数据库"><h3 id="添加数据到数据库"><a href="#添加数据到数据库">添加数据到数据库</a></h3><div class="section-content"><p>现在让我们看一下如何将记录添加到数据库中。这将使用我们页面上的表单完成。</p> <p>在你之前的事件处理器下面,添加以下一行,它设置了一个 <code>submit</code> 事件处理器,当表单被提交时(当提交 <a href="/zh-CN/docs/Web/HTML/Element/button"><code>&lt;button&gt;</code></a> 元素被按下导致表单成功提交),运行一个叫做 <code>addData()</code> 的函数:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 创建一个提交事件处理器,当表单提交时运行 addData() 函数 form.addEventListener("submit", addData); </code></pre></div> <p>现在让我们定义 <code>addData()</code> 函数。在你之前的代码下面添加以下内容:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 addData() 函数 function addData(e) { // 阻止默认行为——我们不希望表单以传统方式提交 e.preventDefault(); // 获取输入字段中输入的值,并将它们存储在一个对象中,准备插入到数据库中 const newItem = { title: titleInput.value, body: bodyInput.value }; // 打开一个读/写事务,准备添加数据 const transaction = db.transaction(["notes_os"], "readwrite"); // 调用已添加到数据库中的 objectStore const objectStore = transaction.objectStore("notes_os"); // 发起请求,将我们的 newItem 对象添加到 objectStore 中 const addRequest = objectStore.add(newItem); addRequest.addEventListener("success", () =&gt; { // 清空表单,为添加下一个条目做好准备 titleInput.value = ""; bodyInput.value = ""; }); // 在事务完成时报告成功,当所有操作完成后 transaction.addEventListener("complete", () =&gt; { console.log("事务完成:数据库修改结束。"); // 通过再次运行 displayData() 来更新数据的显示,以显示新添加的条目 displayData(); }); transaction.addEventListener("error", () =&gt; console.log("事务未成功打开,出现错误"), ); } </code></pre></div> <p>这很复杂;要打破它,我们:</p> <ul> <li>在事件对象上运行 <a href="/zh-CN/docs/Web/API/Event/preventDefault"><code>Event.preventDefault()</code></a> 以停止以传统方式实际提交的表单(这将导致页面刷新并破坏体验)。</li> <li>创建一个表示要输入数据库的记录的对象,并使用表单输入中的值填充它。请注意,我们不必明确包含一个 <code>id</code> 值——正如我们提前详细说明的那样,这是自动填充的。</li> <li>使用 <a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBDatabase/transaction"><code>IDBDatabase.transaction()</code></a> 方法打开 <code>notes</code> 对象存储的 <code>readwrite</code> 事务。此事务对象允许我们访问对象存储,以便我们可以对其执行某些操作,例如添加新记录。</li> <li>使用 <a class="only-in-en-us" title="此页面目前仅提供英文版本" href="/en-US/docs/Web/API/IDBTransaction/objectStore"><code>IDBTransaction.objectStore()</code></a> 方法访问对象库,将结果保存在 <code>objectStore</code> 变量中。</li> <li>使用 <a href="/zh-CN/docs/Web/API/IDBObjectStore/add"><code>IDBObjectStore.add()</code></a> 添加新记录到数据库。这创建了一个请求对象,与我们之前看到的方式相同。</li> <li>在生命周期的关键点为 <code>request</code> 以及 <code>transaction</code> 对象添加事件处理器以运行代码。请求成功后,我们会清除表单输入,以便输入下一个笔记。交易完成后,我们再次运行 <code>displayData()</code> 函数以更新页面上的笔记显示。</li> </ul></div></section><section aria-labelledby="显示数据"><h3 id="显示数据"><a href="#显示数据">显示数据</a></h3><div class="section-content"><p>我们已经在代码中引用了 <code>displayData()</code> 两次,所以我们可能更好地定义它。将其添加到你的代码中,位于上一个函数定义之下:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 displayData() 函数 function displayData() { // 每次更新显示时,我们都清空列表元素的内容 // 如果不这样做,每次添加新笔记时列表中会出现重复项 while (list.firstChild) { list.removeChild(list.firstChild); } // 打开我们的对象存储,然后获取游标——它会迭代存储中的所有数据项 const objectStore = db.transaction("notes_os").objectStore("notes_os"); objectStore.openCursor().addEventListener("success", (e) =&gt; { // 获取游标的引用 const cursor = e.target.result; // 如果还有数据项需要迭代,则继续运行此代码 if (cursor) { // 创建一个列表项、h3 和 p 元素,用于在显示数据项时放置它们 // 构建 HTML 片段,并将其附加到列表中 const listItem = document.createElement("li"); const h3 = document.createElement("h3"); const para = document.createElement("p"); listItem.appendChild(h3); listItem.appendChild(para); list.appendChild(listItem); // 将游标中的数据放入 h3 和 para 中 h3.textContent = cursor.value.title; para.textContent = cursor.value.body; // 将数据项的 ID 存储在 listItem 的一个属性中,以便我们知道 // 这项数据对应哪个条目。这在稍后删除条目时会很有用 listItem.setAttribute("data-note-id", cursor.value.id); // 创建一个按钮,并将其放置在每个 listItem 中 const deleteBtn = document.createElement("button"); listItem.appendChild(deleteBtn); deleteBtn.textContent = "删除"; // 设置事件处理器,当按钮被点击时,运行 deleteItem() 函数 deleteBtn.addEventListener("click", deleteItem); // 迭代到游标中的下一个项 cursor.continue(); } else { // 如果列表为空,则显示“没有存储的笔记”消息 if (!list.firstChild) { const listItem = document.createElement("li"); listItem.textContent = "没有存储的笔记。"; list.appendChild(listItem); } // 如果没有更多的游标项需要迭代,说明所有笔记都已显示 console.log("所有笔记已显示"); } }); } </code></pre></div> <p>再次,让我们打破这个:</p> <ul> <li>首先,我们清空 <a href="/zh-CN/docs/Web/HTML/Element/ul"><code>&lt;ul&gt;</code></a> 元素的内容,然后填充更新的内容。如果你不这样做,那么每次更新时都会添加大量重复内容。</li> <li>接下来,我们 <code>notes</code> 使用 <a href="/en-US/docs/Web/API/IDBDatabase/transaction" class="only-in-en-us" title="此页面目前仅提供英文版本"><code>IDBDatabase.transaction()</code></a> 和 <a href="/en-US/docs/Web/API/IDBTransaction/objectStore" class="only-in-en-us" title="此页面目前仅提供英文版本"><code>IDBTransaction.objectStore()</code></a> 我们一样得到对象存储的引用 <code>addData()</code>,除了这里我们将它们链接在一行中。</li> <li>下一步是使用 <a href="/zh-CN/docs/Web/API/IDBObjectStore/openCursor"><code>IDBObjectStore.openCursor()</code></a> 方法打开对游标的请求——这是一个可用于迭代对象存储中的记录的构造。我们将一个 <code>onsuccess</code> 处理器链接到该行的末尾以使代码更简洁——当成功返回游标时,运行处理器。</li> <li>我们 <a href="/zh-CN/docs/Web/API/IDBCursor"><code>IDBCursor</code></a> 使用 let 获取对游标本身(对象)的引用 <code>cursor = e.target.result</code>。</li> <li>接下来,我们检查光标是否包含来自数据存储区(<code>if(cursor){ ... }</code>)的记录——如果包含,我们创建一个 DOM 片段,用记录中的数据填充它,然后将其插入页面(<code>&lt;ul&gt;</code> 元素内部)。我们还包括一个删除按钮,当单击该按钮时,将通过运行 <code>deleteItem()</code> 函数删除该笔记(我们将在下一节中查看)。</li> <li>在 <code>if</code> 块结束时,我们使用该 <a href="/en-US/docs/Web/API/IDBCursor/continue" class="only-in-en-us" title="此页面目前仅提供英文版本"><code>IDBCursor.continue()</code></a> 方法将光标前进到数据存储区中的下一条记录,然后<code>if</code>再次运行块的内容。如果有另一个要迭代的记录,这会导致它被插入到页面中,然后 <code>continue()</code> 再次运行,依此类推。</li> <li>当没有更多记录要迭代时,<code>cursor</code> 将返回 <code>undefined</code>,因此 <code>else</code> 块将运行(而不是 <code>if</code> 块)。此块检查是否有任何笔记被插入 <code>&lt;ul&gt;</code>——如果没有,它会插入一条消息,说没有存储的笔记。</li> </ul></div></section><section aria-labelledby="删除一条笔记"><h3 id="删除一条笔记"><a href="#删除一条笔记">删除一条笔记</a></h3><div class="section-content"><p>如上所述,当按下笔记的删除按钮时,笔记将被删除。这是通过 <code>deleteItem()</code> 函数实现的,如下所示:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 deleteItem() 函数 function deleteItem(e) { // 获取要删除的任务的 ID。我们需要将其转换为数字,因为在 IDB 中使用时 // IDB 键值对对类型敏感。 const noteId = Number(e.target.parentNode.getAttribute("data-note-id")); // 打开一个数据库事务并删除任务,使用我们上面检索到的 ID 查找它 const transaction = db.transaction(["notes_os"], "readwrite"); const objectStore = transaction.objectStore("notes_os"); const deleteRequest = objectStore.delete(noteId); // 报告数据项已被删除 transaction.addEventListener("complete", () =&gt; { // 删除按钮的父元素 // 也就是列表项,使其不再显示 e.target.parentNode.parentNode.removeChild(e.target.parentNode); console.log(`笔记 ${noteId} 已删除。`); // 如果列表为空,则显示“没有存储的笔记”消息 if (!list.firstChild) { const listItem = document.createElement("li"); listItem.textContent = "没有存储的笔记。"; list.appendChild(listItem); } }); } </code></pre></div> <ul> <li>第一部分可以使用一些解释——我们检索要删除 <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> 的记录的 ID——回想一下记录的 ID 是在第一次显示时保存在 <code>data-note-id</code> 属性中的 <code>&lt;li&gt;</code>。但是,我们需要通过全局内置的 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number"><code>Number()</code></a> 对象传递属性,因为它当前是一个字符串,否则将无法被数据库识别。</li> <li>然后,我们使用我们之前看到的相同模式获取对对象存储的引用,并使用该 <a href="/en-US/docs/Web/API/IDBObjectStore/delete" class="only-in-en-us" title="此页面目前仅提供英文版本"><code>IDBObjectStore.delete()</code></a> 方法从数据库中删除记录,并将 ID 传递给它。</li> <li>当数据库事务完成后,我们从 DOM 中删除笔记的 <code>&lt;li&gt;</code>,然后再次检查以查看 <code>&lt;ul&gt;</code> 是否为空,并根据需要插入笔记。</li> </ul> <p>就是这样了!你的例子现在应该有效。</p> <p>如果你遇到问题,请随时<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/" class="external" target="_blank">查看我们的实例</a>(请参阅<a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.js" class="external" target="_blank">源代码</a>)。</p></div></section><section aria-labelledby="通过_indexeddb_存储复杂数据"><h3 id="通过_indexeddb_存储复杂数据"><a href="#通过_indexeddb_存储复杂数据">通过 IndexedDB 存储复杂数据</a></h3><div class="section-content"><p>如上所述,IndexedDB 可用于存储不仅仅是简单的文本字符串。你可以存储任何你想要的东西,包括复杂的对象,如视频或图像 blob。并且它比任何其他类型的数据更难实现。</p> <p>为了演示如何操作,我们编写了另一个名为 <a href="https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/indexeddb/video-store" class="external" target="_blank">IndexedDB 视频存储</a>的(也可<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/" class="external" target="_blank">在线运行</a>)。首次运行示例时,它会从网络下载所有视频,将它们存储在 IndexedDB 数据库中,然后在 UI 内部 <a href="/zh-CN/docs/Web/HTML/Element/video"><code>&lt;video&gt;</code></a> 元素中显示视频。第二次运行它时,它会在数据库中找到视频并从那里获取它们而不是显示它们——这使得后续加载更快,占用空间更少。</p> <p>让我们来看看这个例子中最有趣的部分。我们不会全部看——它的很多内容与上一个示例类似,代码注释得很好。</p> <ol> <li> <p>对于这个简单的例子,我们已经存储了视频的名称以获取数组对象:</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>const videos = [ { name: "crystal" }, { name: "elf" }, { name: "frog" }, { name: "monster" }, { name: "pig" }, { name: "rabbit" }, ]; </code></pre></div> </li> <li> <p>首先,一旦数据库成功打开,我们就运行 <code>init()</code> 函数。这会遍历不同的视频名称,尝试加载由 <code>videos</code> 数据库中的每个名称标识的记录。</p> <p>如果在数据库中找到每个视频(通过查看 <code>request.result</code> 评估是否容易检查 <code>true</code>——如果记录不存在,那么 <code>undefined</code>),视频文件(存储为 blob)和视频名称将直接传递给 <code>displayVideo()</code> 函数以放置它们在用户界面中。如果没有,视频名称将传递给 <code>fetchVideoFromNetwork()</code> 函数……你猜对了——从网络中获取视频。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>function init() { // 逐个遍历视频名称 for (const video of videos) { // 打开事务,获取对象存储,并通过名称获取每个视频 const objectStore = db.transaction("videos_os").objectStore("videos_os"); const request = objectStore.get(video.name); request.addEventListener("success", () =&gt; { // 如果结果存在于数据库中(不是 undefined) if (request.result) { // 从 IDB 获取视频并使用 displayVideo() 显示它们 console.log("从 IDB 获取视频"); displayVideo( request.result.mp4, request.result.webm, request.result.name, ); } else { // 从网络获取视频 fetchVideoFromNetwork(video); } }); } } </code></pre></div> </li> <li> <p>以下片段是从内部 <code>fetchVideoFromNetwork()</code> 获取的——这里我们使用两个单独的 <a href="/zh-CN/docs/Web/API/Window/fetch"><code>fetch()</code></a> 请求获取视频的 MP4 和 WebM 版本。然后,我们使用 <a href="/zh-CN/docs/Web/API/Blob"><code>Body.blob()</code></a> 方法将每个响应的主体提取为 blob,为我们提供可以在以后存储和显示的视频的对象表示。</p> <p>我们在这里遇到了一个问题——这两个请求都是异步的,但我们只想在两个 promise 都兑现时尝试显示或存储视频。幸运的是,有一种处理这种问题的内置方法——<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all"><code>Promise.all()</code></a>。这需要一个参数——引用你要检查放置在数组中的所有 promise 的兑现状态——并返回一个会在所有单独的 promise 都兑现时兑现的 promise。</p> <p>在 promise 的 <code>then()</code> 处理器中,我们像之前一样调用 <code>displayVideo()</code> 函数以在 UI 中显示视频,然后我们也调用 <code>storeVideo()</code> 函数将这些视频存储在数据库中。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 使用 fetch() 函数获取视频的 MP4 和 WebM 版本, // 然后将其响应体作为 blob 公开 const mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) =&gt; response.blob(), ); const webmBlob = fetch(`videos/${video.name}.webm`).then((response) =&gt; response.blob(), ); // 只有在两个 Promise 都完成后才运行下一段代码 Promise.all([mp4Blob, webmBlob]).then((values) =&gt; { // 使用 displayVideo() 显示从网络获取的视频 displayVideo(values[0], values[1], video.name); // 使用 storeVideo() 将视频存储到 IDB 中 storeVideo(values[0], values[1], video.name); }); </code></pre></div> </li> <li> <p>我们首先看一下 <code>storeVideo()</code>。这与你在上一个示例中看到的用于向数据库添加数据的模式非常相似——我们打开一个 <code>readwrite</code> 事务并获取 <code>videos_os</code> 对象存储的引用,创建一个表示要添加到数据库的记录的对象,然后使用 <a href="/zh-CN/docs/Web/API/IDBObjectStore/add"><code>IDBObjectStore.add()</code></a> 添加它。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 storeVideo() 函数 function storeVideo(mp4, webm, name) { // 打开事务,获取对象存储;将其设置为 readwrite,以便我们可以写入 IDB const objectStore = db .transaction(["videos_os"], "readwrite") .objectStore("videos_os"); // 使用 add() 将记录添加到 IDB 中 const request = objectStore.add({ mp4, webm, name }); request.addEventListener("success", () =&gt; console.log("记录添加尝试完成")); request.addEventListener("error", () =&gt; console.error(request.error)); } </code></pre></div> </li> <li> <p>最后,我们的 <code>displayVideo()</code> 用于创建在 UI 中插入视频然后将它们附加到页面所需的 DOM 元素。最有趣的部分如下所示——要在 <code>&lt;video&gt;</code> 元素中实际显示我们的视频 blob,我们需要使用 <a href="/zh-CN/docs/Web/API/URL/createObjectURL_static" title="URL.createObjectURL()"><code>URL.createObjectURL()</code></a> 方法创建对象 URL(指向存储在内存中的视频 blob 的内部 URL)。完成后,我们可以将对象 URL 设置为 <a href="/zh-CN/docs/Web/HTML/Element/source"><code>&lt;source&gt;</code></a> 元素 <code>src</code> 属性的值,并且它可以正常工作。</p> </li> </ol> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 定义 displayVideo() 函数 function displayVideo(mp4Blob, webmBlob, title) { // 从 blobs 创建对象 URL const mp4URL = URL.createObjectURL(mp4Blob); const webmURL = URL.createObjectURL(webmBlob); // 创建 DOM 元素以将视频嵌入到页面中 const article = document.createElement("article"); const h2 = document.createElement("h2"); h2.textContent = title; const video = document.createElement("video"); video.controls = true; const source1 = document.createElement("source"); source1.src = mp4URL; source1.type = "video/mp4"; const source2 = document.createElement("source"); source2.src = webmURL; source2.type = "video/webm"; // 将 DOM 元素嵌入到页面中 section.appendChild(article); article.appendChild(h2); article.appendChild(video); video.appendChild(source1); video.appendChild(source2); } </code></pre></div></div></section><section aria-labelledby="离线文件存储"><h2 id="离线文件存储"><a href="#离线文件存储">离线文件存储</a></h2><div class="section-content"><p>上面的示例已经说明了如何创建一个将大型资产存储在 IndexedDB 数据库中的应用程序,从而无需多次下载它们。这已经是对用户体验的一个很大的改进,但仍然有一件事——每次访问网站时仍然需要下载主要的 HTML、CSS 和 JavaScript 文件,这意味着当没有网络连接时,它将无法工作。</p> <p> <img src="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/ff-offline.png" alt="Firefox 离线屏幕,左侧有一幅卡通角色的插图,该角色右手拿着一个两脚插头,左手拿着一个两脚插座。右侧有一个“离线模式”消息和一个标记为“再试一次”的按钮。" width="765" height="307" loading="lazy"> </p> <p>这就是 <a href="/zh-CN/docs/Web/API/Service_Worker_API">service worker</a> 和密切相关的 <a href="/zh-CN/docs/Web/API/Cache">Cache API</a> 的用武之地。</p> <p>service worker 是再被浏览器访问时针对特定来源(网站或某个域的网站的一部分)进行注册的 JavaScript 文件。注册后,它可以控制该来源的可用页面。它通过坐在加载的页面和网络之间以及拦截针对该来源的网络请求来实现这一点。</p> <p>当它拦截一个请求时,它可以做任何你想做的事情(参见<a href="/zh-CN/docs/Web/API/Service_Worker_API#%E5%85%B6%E4%BB%96%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF">用例思路</a>),但经典的例子是离线保存网络响应,然后提供响应请求而不是来自网络的响应。实际上,它允许你使网站完全脱机工作。</p> <p>Cache API 是另一种客户端存储机制,略有不同——它旨在保存 HTTP 响应,因此与 service worker 一起工作得非常好。</p></div></section><section aria-labelledby="service_worker_示例"><h3 id="service_worker_示例"><a href="#service_worker_示例">service worker 示例</a></h3><div class="section-content"><p>让我们看一个例子,让你对这可能是什么样子有所了解。我们已经创建了另一个版本的视频存储示例,我们在上一节中看到了——这个功能完全相同,只是它还通过 service worker 将 Cache、CSS 和 JavaScript 保存在 Cache API 中,允许示例脱机运行!</p> <p>请参阅 <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" class="external" target="_blank">IndexedDB 视频存储,service worker 在其中运行</a>,并且还可以<a href="https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/cache-sw/video-store-offline" class="external" target="_blank">查看源代码</a>。</p> <h4 id="注册_service_worker">注册 service worker</h4> <p>首先要注意的是,在主 JavaScript 文件中放置了一些额外的代码(请参阅 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js" class="external" target="_blank">index.js</a>)。首先,我们进行特性检测测试,以查看 <code>serviceWorker</code> 的 <a href="/zh-CN/docs/Web/API/Navigator"><code>Navigator</code></a> 对象中是否有该成员。如果返回 true,那么我们知道至少支持 service worker 的基础知识。在这里,我们使用该 <a href="/zh-CN/docs/Web/API/ServiceWorkerContainer/register"><code>ServiceWorkerContainer.register()</code></a> 方法将 <code>sw.js</code> 文件中包含的 service worker 注册到它所驻留的源,因此它可以控制与它或子目录相同的目录中的页面。当其 promise 兑现时,service worker 被视为已注册。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>// 注册 service worker 以控制使网站离线工作 if ("serviceWorker" in navigator) { navigator.serviceWorker .register( "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js", ) .then(() =&gt; console.log("已注册 service worker")); } </code></pre></div> <div class="notecard note"> <p><strong>备注:</strong> <code>sw.js</code> 文件的给定路径是相对于站点源的,而不是包含代码的 JavaScript 文件。service worker 在 <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>。来源是 <code>https://mdn.github.io</code>,因此给定的路径必须是 <code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>。如果你想在自己的服务器上托管此示例,则必须相应地更改此示例。这是相当令人困惑的,但出于安全原因,它必须以这种方式工作。</p> </div> <h4 id="安装_service_worker">安装 service worker</h4> <p>下次访问 service worker 控制下的任何页面时(例如,重新加载示例时),将针对该页面安装 service worker,这意味着它将开始控制它。发生这种情况时,<code>install</code> 会向 service worker 发起一个事件;你可以在 service worker 本身内编写代码来响应安装。</p> <p>让我们看一下 <a href="https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js" class="external" target="_blank">sw.js</a> 文件(service worker)中的一个例子。你将看到 install 监听器是注册到 <code>self</code> 上的。<code>self</code> 关键字是一种从 service worker 文件内部引用 service worker 的全局作用域的方法。</p> <p>在 <code>install</code> 处理器内部,我们使用 <a href="/zh-CN/docs/Web/API/ExtendableEvent/waitUntil"><code>ExtendableEvent.waitUntil()</code></a> 事件对象上可用的方法来表示浏览器不应该完成 service worker 的安装,直到其中的 promise 成功兑现。</p> <p>这是我们在运行中看到 Cache API 的地方。我们使用该 <a href="/zh-CN/docs/Web/API/CacheStorage/open"><code>CacheStorage.open()</code></a> 方法打开一个可以存储响应的新缓存对象(类似于 IndexedDB 对象存储)。该 promise 以表示 <code>video-store</code> 缓存的 <a href="/zh-CN/docs/Web/API/Cache"><code>Cache</code></a> 对象来兑现。然后,我们使用该 <a href="/zh-CN/docs/Web/API/Cache/addAll"><code>Cache.addAll()</code></a> 方法获取一系列资产并将其响应添加到缓存中。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>self.addEventListener("install", (e) =&gt; { e.waitUntil( caches .open("video-store") .then((cache) =&gt; cache.addAll([ "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js", "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css", ]), ), ); }); </code></pre></div> <p>这就是现在,安装完成。</p> <h4 id="响应未来的请求">响应未来的请求</h4> <p>在我们的 HTML 页面上注册并安装了 service worker,并且所有相关资产都添加到我们的缓存中,我们几乎准备好了。还有一件事要做,写一些代码来响应进一步的网络请求。</p> <p>这就是第二位代码的 <code>sw.js</code> 作用。我们向 service worker 全局作用域添加另一个监听器,会在 <code>fetch</code> 引发事件时运行处理函数。只要浏览器在 service worker 注册的目录中请求资产,就会发生这种情况。</p> <p>在处理器内部,我们首先记录所请求资产的 URL。然后,我们使用 <a href="/zh-CN/docs/Web/API/FetchEvent/respondWith"><code>FetchEvent.respondWith()</code></a> 方法为请求提供自定义响应。</p> <p>在这个块中,我们使用 <a href="/zh-CN/docs/Web/API/CacheStorage/match"><code>CacheStorage.match()</code></a> 来检查是否可以在任何缓存中找到匹配的请求(即匹配 URL)。如果找到匹配,promise 以匹配的响应兑现,如果未找到,则兑现 <code>undefined</code>。</p> <p>如果找到匹配项,我们只需将其作为自定义响应返回。如果没有,我们从网络中 <a href="/zh-CN/docs/Web/API/Window/fetch">fetch()</a> 响应并返回该响应。</p> <div class="code-example"><div class="example-header"><span class="language-name">js</span></div><pre class="brush: js notranslate"><code>self.addEventListener("fetch", (e) =&gt; { console.log(e.request.url); e.respondWith( caches.match(e.request).then((response) =&gt; response || fetch(e.request)), ); }); </code></pre></div> <p>这就是我们的 service worker。你可以使用它们处理更多的负载——有关详细信息,请参阅 <a href="https://github.com/mdn/serviceworker-cookbook/" class="external" target="_blank">service worker 手册</a>。感谢 Paul Kinlan 的<a href="https://developers.google.com/web/fundamentals/codelabs/offline/" class="external" target="_blank">为你的 Web 应用程序添加 service worker 和离线浏览</a>一文给予这一简单示例的启发。</p> <h4 id="测试离线示例">测试离线示例</h4> <p>要测试我们的 <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/" class="external" target="_blank">service worker 示例</a>,你需要加载它几次以确保它已安装。完成后,你可以:</p> <ul> <li>尝试拔掉网络连接/关闭 Wifi。</li> <li>如果你使用的是 Firefox,请选择<em>文件&gt;脱机工作</em>。</li> <li>转到开发者工具,然后选择<em>应用 &gt; Service Workers</em>,如果你使用的是 Chrome,请选中<em>离线</em>。</li> </ul> <p>如果再次刷新示例页面,你仍应该看到它加载得很好。所有内容都是脱机存储的——缓存中的页面资源以及 IndexedDB 数据库中的视频。</p></div></section><section aria-labelledby="总结"><h2 id="总结"><a href="#总结">总结</a></h2><div class="section-content"><p>现在就是这些。我们希望你能感觉到我们对客户端存储技术的介绍很有用。</p></div></section><section aria-labelledby="参见"><h2 id="参见"><a href="#参见">参见</a></h2><div class="section-content"><ul> <li><a href="/zh-CN/docs/Web/API/Web_Storage_API">Web 存储 API</a></li> <li><a href="/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB API</a></li> <li><a href="/zh-CN/docs/Web/HTTP/Cookies">Cookie</a></li> <li><a href="/zh-CN/docs/Web/API/Service_Worker_API">Service worker API</a></li> </ul><ul class="prev-next"> <li><a class="button secondary" href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs"><span class="button-wrap"> 上一页 </span></a></li> <li><a class="button secondary" href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs"><span class="button-wrap"> 概述:客户端 Web API</span></a></li> </ul></div></section></article><aside class="article-footer"><div class="article-footer-inner"><div class="svg-container"><svg xmlns="http://www.w3.org/2000/svg" width="162" height="162" viewBox="0 0 162 162" fill="none" role="none"><mask id="b" fill="#fff"><path d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z"></path></mask><path stroke="url(#a)" stroke-dasharray="6, 6" stroke-width="2" d="M97.203 47.04c8.113-7.886 18.004-13.871 28.906-17.492a78 78 0 0 1 33.969-3.39c11.443 1.39 22.401 5.295 32.024 11.411s17.656 14.28 23.476 23.86c5.819 9.579 9.269 20.318 10.083 31.385a69.85 69.85 0 0 1-5.387 32.44c-4.358 10.272-11.115 19.443-19.747 26.801-8.632 7.359-18.908 12.709-30.034 15.637l-6.17-21.698c7.666-2.017 14.746-5.703 20.694-10.773 5.948-5.071 10.603-11.389 13.606-18.467a48.14 48.14 0 0 0 3.712-22.352c-.561-7.625-2.938-15.025-6.948-21.625s-9.544-12.226-16.175-16.44-14.181-6.904-22.065-7.863a53.75 53.75 0 0 0-23.405 2.336c-7.513 2.495-14.327 6.62-19.918 12.053z" mask="url(#b)" style="stroke:url(#a)" transform="translate(-63.992 -25.587)"></path><ellipse cx="8.066" cy="111.597" fill="var(--background-tertiary)" rx="53.677" ry="53.699" transform="matrix(.71707 -.697 .7243 .6895 0 0)"></ellipse><g clip-path="url(#c)" transform="translate(-63.992 -25.587)"><path fill="#9abff5" d="m144.256 137.379 32.906 12.434a4.41 4.41 0 0 1 2.559 5.667l-9.326 24.679a4.41 4.41 0 0 1-5.667 2.559l-8.226-3.108-2.332 6.17c-.466 1.233-.375 1.883-1.609 1.417l-2.253-.527c-.411-.155-.95-.594-1.206-1.161l-4.734-10.484-12.545-4.741a4.41 4.41 0 0 1-2.559-5.667l9.325-24.679a4.41 4.41 0 0 1 5.667-2.559m9.961 29.617 8.227 3.108 3.264-8.638-.498-6.768-4.113-1.555.548 7.258-4.319-1.632zm-12.339-4.663 8.226 3.108 3.264-8.637-.498-6.769-4.113-1.554.548 7.257-4.319-1.632z"></path></g><g clip-path="url(#d)" transform="translate(-63.992 -25.587)"><path fill="#81b0f3" d="M135.35 60.136 86.67 41.654c-3.346-1.27-7.124.428-8.394 3.775L64.414 81.938c-1.27 3.347.428 7.125 3.774 8.395l12.17 4.62-3.465 9.128c-.693 1.826-1.432 2.457.394 3.15l3.014 1.625c.609.231 1.637.274 2.477-.104l15.53-6.983 18.56 7.047c3.346 1.27 7.124-.428 8.395-3.775l13.862-36.51c1.27-3.346-.428-7.124-3.775-8.395M95.261 83.207l-12.17-4.62 4.852-12.779 7.19-7.017 6.085 2.31-7.725 7.51 6.389 2.426zm18.255 6.93-12.17-4.62 4.852-12.778 7.189-7.017 6.085 2.31-7.725 7.51 6.39 2.426z"></path></g><defs><clipPath id="c"><path fill="#fff" d="m198.638 146.586-65.056-24.583-24.583 65.057 65.056 24.582z"></path></clipPath><clipPath id="d"><path fill="#fff" d="m66.438 14.055 96.242 36.54-36.54 96.243-96.243-36.54z"></path></clipPath><linearGradient id="a" x1="97.203" x2="199.995" y1="47.04" y2="152.793" gradientUnits="userSpaceOnUse"><stop stop-color="#086DFC"></stop><stop offset="0.246" stop-color="#2C81FA"></stop><stop offset="0.516" stop-color="#5497F8"></stop><stop offset="0.821" stop-color="#80B0F6"></stop><stop offset="1" stop-color="#9ABFF5"></stop></linearGradient></defs></svg></div><h2>Help improve MDN</h2><fieldset class="feedback"><label>Was this page helpful to you?</label><div class="button-container"><button type="button" class="button primary has-icon yes"><span class="button-wrap"><span class="icon icon-thumbs-up "></span>Yes</span></button><button type="button" class="button primary has-icon no"><span class="button-wrap"><span class="icon icon-thumbs-down "></span>No</span></button></div></fieldset><a class="contribute" href="https://github.com/mdn/translated-content/blob/main/CONTRIBUTING.md" title="This will take you to our contribution guidelines on GitHub." target="_blank" rel="noopener noreferrer">Learn how to contribute</a>.<p class="last-modified-date">This page was last modified on<!-- --> <time dateTime="2024-09-22T12:25:49.000Z">2024年9月22日</time> by<!-- --> <a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/contributors.txt" rel="nofollow">MDN contributors</a>.</p><div id="on-github" class="on-github"><a href="https://github.com/mdn/translated-content/blob/main/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md?plain=1" title="Folder: zh-cn/learn/javascript/client-side_web_apis/client-side_storage (Opens in a new tab)" target="_blank" rel="noopener noreferrer">View this page on GitHub</a> <!-- -->•<!-- --> <a href="https://github.com/mdn/translated-content/issues/new?template=page-report-zh-cn.yml&amp;mdn-url=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage&amp;metadata=%3C%21--+Do+not+make+changes+below+this+line+--%3E%0A%3Cdetails%3E%0A%3Csummary%3EPage+report+details%3C%2Fsummary%3E%0A%0A*+Folder%3A+%60zh-cn%2Flearn%2Fjavascript%2Fclient-side_web_apis%2Fclient-side_storage%60%0A*+MDN+URL%3A+https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FLearn%2FJavaScript%2FClient-side_web_APIs%2FClient-side_storage%0A*+GitHub+URL%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Ftranslated-content%2Fblob%2Fmain%2Ffiles%2Fzh-cn%2Flearn%2Fjavascript%2Fclient-side_web_apis%2Fclient-side_storage%2Findex.md%0A*+Last+commit%3A+https%3A%2F%2Fgithub.com%2Fmdn%2Ftranslated-content%2Fcommit%2F3094da9c8210d2598a55afbfde1aa988e280bb05%0A*+Document+last+modified%3A+2024-09-22T12%3A25%3A49.000Z%0A%0A%3C%2Fdetails%3E" title="This will take you to GitHub to file a new issue." target="_blank" rel="noopener noreferrer">Report a problem with this content</a></div></div></aside></main></div></div><footer id="nav-footer" class="page-footer"><div class="page-footer-grid"><div class="page-footer-logo-col"><a href="/" class="mdn-footer-logo" aria-label="MDN homepage"><svg width="48" height="17" viewBox="0 0 48 17" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="mdn-footer-logo-svg">MDN logo</title><path d="M20.04 16.512H15.504V10.416C15.504 9.488 15.344 8.824 15.024 8.424C14.72 8.024 14.264 7.824 13.656 7.824C12.92 7.824 12.384 8.064 12.048 8.544C11.728 9.024 11.568 9.64 11.568 10.392V14.184H13.008V16.512H8.472V10.416C8.472 9.488 8.312 8.824 7.992 8.424C7.688 8.024 7.232 7.824 6.624 7.824C5.872 7.824 5.336 8.064 5.016 8.544C4.696 9.024 4.536 9.64 4.536 10.392V14.184H6.6V16.512H0V14.184H1.44V8.04H0.024V5.688H4.536V7.32C5.224 6.088 6.32 5.472 7.824 5.472C8.608 5.472 9.328 5.664 9.984 6.048C10.64 6.432 11.096 7.016 11.352 7.8C11.992 6.248 13.168 5.472 14.88 5.472C15.856 5.472 16.72 5.776 17.472 6.384C18.224 6.992 18.6 7.936 18.6 9.216V14.184H20.04V16.512Z" fill="currentColor"></path><path d="M33.6714 16.512H29.1354V14.496C28.8314 15.12 28.3834 15.656 27.7914 16.104C27.1994 16.536 26.4154 16.752 25.4394 16.752C24.0154 16.752 22.8954 16.264 22.0794 15.288C21.2634 14.312 20.8554 12.984 20.8554 11.304C20.8554 9.688 21.2554 8.312 22.0554 7.176C22.8554 6.04 24.0634 5.472 25.6794 5.472C26.5594 5.472 27.2794 5.648 27.8394 6C28.3994 6.352 28.8314 6.8 29.1354 7.344V2.352H26.9754V0H32.2314V14.184H33.6714V16.512ZM29.1354 11.04V10.776C29.1354 9.88 28.8954 9.184 28.4154 8.688C27.9514 8.176 27.3674 7.92 26.6634 7.92C25.9754 7.92 25.3674 8.176 24.8394 8.688C24.3274 9.2 24.0714 10.008 24.0714 11.112C24.0714 12.152 24.3114 12.944 24.7914 13.488C25.2714 14.032 25.8394 14.304 26.4954 14.304C27.3114 14.304 27.9514 13.96 28.4154 13.272C28.8954 12.584 29.1354 11.84 29.1354 11.04Z" fill="currentColor"></path><path d="M47.9589 16.512H41.9829V14.184H43.4229V10.416C43.4229 9.488 43.2629 8.824 42.9429 8.424C42.6389 8.024 42.1829 7.824 41.5749 7.824C40.8389 7.824 40.2709 8.056 39.8709 8.52C39.4709 8.968 39.2629 9.56 39.2469 10.296V14.184H40.6869V16.512H34.7109V14.184H36.1509V8.04H34.5909V5.688H39.2469V7.344C39.9669 6.096 41.1269 5.472 42.7269 5.472C43.7509 5.472 44.6389 5.776 45.3909 6.384C46.1429 6.992 46.5189 7.936 46.5189 9.216V14.184H47.9589V16.512Z" fill="currentColor"></path></svg></a><p>Your blueprint for a better internet.</p><ul class="social-icons"><li><a href="https://mozilla.social/@mdn" target="_blank" rel="me noopener noreferrer"><span class="icon icon-mastodon"></span><span class="visually-hidden">MDN on Mastodon</span></a></li><li><a href="https://twitter.com/mozdevnet" target="_blank" rel="noopener noreferrer"><span class="icon icon-twitter-x"></span><span class="visually-hidden">MDN on X (formerly Twitter)</span></a></li><li><a href="https://github.com/mdn/" target="_blank" rel="noopener noreferrer"><span class="icon icon-github-mark-small"></span><span class="visually-hidden">MDN on GitHub</span></a></li><li><a href="/en-US/blog/rss.xml" target="_blank"><span class="icon icon-feed"></span><span class="visually-hidden">MDN Blog RSS Feed</span></a></li></ul></div><div class="page-footer-nav-col-1"><h2 class="footer-nav-heading">MDN</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a href="/en-US/about">About</a></li><li class="footer-nav-item"><a href="/en-US/blog/">Blog</a></li><li class="footer-nav-item"><a href="https://www.mozilla.org/en-US/careers/listings/?team=ProdOps" target="_blank" rel="noopener noreferrer">Careers</a></li><li class="footer-nav-item"><a href="/en-US/advertising">Advertise with us</a></li></ul></div><div class="page-footer-nav-col-2"><h2 class="footer-nav-heading">Support</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="https://support.mozilla.org/products/mdn-plus">Product help</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/zh-CN/docs/MDN/Community/Issues">Report an issue</a></li></ul></div><div class="page-footer-nav-col-3"><h2 class="footer-nav-heading">Our communities</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/en-US/community">MDN Community</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="https://discourse.mozilla.org/c/mdn/236" target="_blank" rel="noopener noreferrer">MDN Forum</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/discord" target="_blank" rel="noopener noreferrer">MDN Chat</a></li></ul></div><div class="page-footer-nav-col-4"><h2 class="footer-nav-heading">Developers</h2><ul class="footer-nav-list"><li class="footer-nav-item"><a class="footer-nav-link" href="/zh-CN/docs/Web">Web Technologies</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/zh-CN/docs/Learn">Learn Web Development</a></li><li class="footer-nav-item"><a class="footer-nav-link" href="/zh-CN/plus">MDN Plus</a></li><li class="footer-nav-item"><a href="https://hacks.mozilla.org/" target="_blank" rel="noopener noreferrer">Hacks Blog</a></li></ul></div><div class="page-footer-moz"><a href="https://www.mozilla.org/" class="footer-moz-logo-link" target="_blank" rel="noopener noreferrer"><svg width="112" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="mozilla-footer-logo-svg">Mozilla logo</title><path d="M41.753 14.218c-2.048 0-3.324 1.522-3.324 4.157 0 2.423 1.119 4.286 3.29 4.286 2.082 0 3.447-1.678 3.447-4.347 0-2.826-1.522-4.096-3.413-4.096Zm54.89 7.044c0 .901.437 1.618 1.645 1.618 1.427 0 2.949-1.024 3.044-3.352-.649-.095-1.365-.185-2.02-.185-1.426-.005-2.668.397-2.668 1.92Z" fill="currentColor"></path><path d="M0 0v32h111.908V0H0Zm32.56 25.426h-5.87v-7.884c0-2.423-.806-3.352-2.39-3.352-1.924 0-2.702 1.365-2.702 3.324v4.868h1.864v3.044h-5.864v-7.884c0-2.423-.806-3.352-2.39-3.352-1.924 0-2.702 1.365-2.702 3.324v4.868h2.669v3.044H6.642v-3.044h1.863v-7.918H6.642V11.42h5.864v2.11c.839-1.489 2.3-2.39 4.252-2.39 2.02 0 3.878.963 4.566 3.01.778-1.862 2.361-3.01 4.566-3.01 2.512 0 4.812 1.522 4.812 4.84v6.402h1.863v3.044h-.005Zm9.036.307c-4.314 0-7.296-2.635-7.296-7.106 0-4.096 2.484-7.481 7.514-7.481s7.481 3.38 7.481 7.29c0 4.472-3.228 7.297-7.699 7.297Zm22.578-.307H51.942l-.403-2.11 7.7-8.846h-4.376l-.621 2.17-2.888-.313.498-4.907h12.294l.313 2.11-7.767 8.852h4.533l.654-2.172 3.167.308-.872 4.908Zm7.99 0h-4.191v-5.03h4.19v5.03Zm0-8.976h-4.191v-5.03h4.19v5.03Zm2.618 8.976 6.054-21.358h3.945l-6.054 21.358h-3.945Zm8.136 0 6.048-21.358h3.945l-6.054 21.358h-3.939Zm21.486.307c-1.863 0-2.887-1.085-3.072-2.792-.805 1.427-2.232 2.792-4.498 2.792-2.02 0-4.314-1.085-4.314-4.006 0-3.447 3.323-4.253 6.518-4.253.778 0 1.584.034 2.3.124v-.465c0-1.427-.034-3.133-2.3-3.133-.84 0-1.488.061-2.143.402l-.453 1.578-3.195-.34.549-3.224c2.45-.996 3.692-1.27 5.992-1.27 3.01 0 5.556 1.55 5.556 4.75v6.083c0 .805.314 1.085.963 1.085.184 0 .375-.034.587-.095l.034 2.11a5.432 5.432 0 0 1-2.524.654Z" fill="currentColor"></path></svg></a><ul class="footer-moz-list"><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Website Privacy Notice</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Cookies</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/legal/terms/mozilla" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Legal</a></li><li class="footer-moz-item"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="footer-moz-link" target="_blank" rel="noopener noreferrer">Community Participation Guidelines</a></li></ul></div><div class="page-footer-legal"><p id="license" class="page-footer-legal-text">Visit<!-- --> <a href="https://www.mozilla.org" target="_blank" rel="noopener noreferrer">Mozilla Corporation’s</a> <!-- -->not-for-profit parent, the<!-- --> <a target="_blank" rel="noopener noreferrer" href="https://foundation.mozilla.org/">Mozilla Foundation</a>.<br/>Portions of this content are ©1998–<!-- -->2024<!-- --> by individual mozilla.org contributors. Content available under<!-- --> <a href="/zh-CN/docs/MDN/Writing_guidelines/Attrib_copyright_license">a Creative Commons license</a>.</p></div></div></footer></div><script type="application/json" id="hydration">{"url":"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","doc":{"isMarkdown":true,"isTranslated":true,"isActive":true,"flaws":{},"title":"客户端存储","mdn_url":"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","locale":"zh-CN","native":"中文 (简体)","sidebarHTML":"<ol><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web\">新手请从这开始!</a></li><li><details><summary>Web 入门</summary><ol><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web\">Web 入门</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/Installing_basic_software\">安装基础软件</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like\">你的网站会是什么样子?</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files\">处理文件</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/HTML_basics\">HTML 基础</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/CSS_basics\">CSS 基础</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/JavaScript_basics\">JavaScript 基础</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/Publishing_your_website\">发布你的网站</a></li><li><a href=\"/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works\">万维网是如何工作的</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/HTML\">HTML——构建 Web</a></li><li><details><summary>HTML 介绍</summary><ol><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML\">HTML 简介</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started\">开始学习 HTML</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML\">“头”里有什么——HTML 元信息</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/HTML_text_fundamentals\">HTML 文本处理基础</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks\">创建超链接</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Advanced_text_formatting\">文本格式进阶</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure\">文档与网站架构</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML\">HTML 调试</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Marking_up_a_letter\">标记信件</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Structuring_a_page_of_content\">构建网页内容</a></li></ol></details></li><li><details><summary>多媒体与嵌入</summary><ol><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding\">多媒体与嵌入</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML\">HTML 中的图片</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content\">视频和音频内容</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies\">从 object 到 iframe——其他嵌入技术</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web\">向 web 中添加矢量图形</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images\">响应式图片</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page\">Mozilla 欢迎页面</a></li></ol></details></li><li><details><summary>HTML 表格</summary><ol><li><a href=\"/zh-CN/docs/Learn/HTML/Tables\">HTML 表格</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Tables/Basics\">HTML 表格基础</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Tables/Advanced\">HTML 表格进阶特性和无障碍</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Tables/Structuring_planet_data\">作业:构建行星数据</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/CSS\">CSS——设计 Web</a></li><li><details><summary>CSS 第一步</summary><ol><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps\">CSS 入门概述</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps/What_is_CSS\">什么是 CSS?</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps/Getting_started\">让我们开始 CSS 的学习之旅</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps/How_CSS_is_structured\">CSS 的组成</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps/How_CSS_works\">CSS 如何运行</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/First_steps/Styling_a_biography_page\">运用你的新知识</a></li></ol></details></li><li><details><summary>CSS 基础</summary><ol><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks\">CSS 构建</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Selectors\">CSS 选择器</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Type_Class_and_ID_Selectors\">类型、类和 ID 选择器</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Attribute_selectors\">属性选择器</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements\">伪类和伪元素</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Combinators\">关系选择器</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance\">层叠、优先级与继承</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Cascade_layers\">层叠层</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model\">盒模型</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Backgrounds_and_borders\">背景与边框</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Handling_different_text_directions\">处理不同方向的文本</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Overflowing_content\">溢出的内容</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Values_and_units\">CSS 值和单位</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Sizing_items_in_CSS\">在 CSS 中调整大小</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Images_media_form_elements\">图像、媒体和表单元素</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Styling_tables\">样式化表格</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Advanced_styling_effects\">高级区块效果</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Debugging_CSS\">调试 CSS</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Organizing\">组织 CSS</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Fundamental_CSS_comprehension\">基本的 CSS 理解</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/Creating_fancy_letterheaded_paper\">创建精美的信纸</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Building_blocks/A_cool_looking_box\">一个漂亮的盒子</a></li></ol></details></li><li><details><summary>样式化文本</summary><ol><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text\">为文本添加样式(样式化文本)</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text/Fundamentals\">基本文本和字体样式</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text/Styling_lists\">为列表添加样式</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text/Styling_links\">样式化链接</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text/Web_fonts\">Web 字体</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Styling_text/Typesetting_a_homepage\">作业:排版社区大学首页</a></li></ol></details></li><li><details><summary>CSS 排版</summary><ol><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout\">CSS 布局</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Introduction\">介绍 CSS 布局</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Normal_Flow\">常规流布局</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox\">弹性盒子</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Grids\">网格</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Floats\">浮动</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Positioning\">定位</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Multiple-column_Layout\">多列布局</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Responsive_Design\">响应式设计</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Media_queries\">媒体查询入门指南</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Legacy_Layout_Methods\">传统的布局方法</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Supporting_Older_Browsers\">支持旧浏览器</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/CSS_layout/Fundamental_Layout_Comprehension\">作业:基本布局理解</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/JavaScript\">JavaScript——用户端动态脚本</a></li><li><details><summary>JavaScript 第一步</summary><ol><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps\">JavaScript 第一步</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/What_is_JavaScript\">什么是 JavaScript?</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash\">JavaScript 初体验</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/What_went_wrong\">查找并解决 JavaScript 代码的错误</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Variables\">如何存储你需要的信息——变量</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Math\">JavaScript 中的基础数学 — 数字和操作符</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Strings\">文本处理——JavaScript 中的字符串</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Useful_string_methods\">有用的字符串方法</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Arrays\">数组</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/First_steps/Silly_story_generator\">笑话生成器</a></li></ol></details></li><li><details><summary>JavaScript 基础</summary><ol><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks\">创建 JavaScript 代码块</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/conditionals\">在代码中做决定——条件语句</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Looping_code\">循环吧,代码</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Functions\">函数——可复用的代码块</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Build_your_own_function\">创建你自己的函数</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Return_values\">函数返回值</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Events\">事件介绍</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Event_bubbling\">事件冒泡</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks/Image_gallery\">图片库</a></li></ol></details></li><li><details><summary>JavaScript 对象介绍</summary><ol><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects\">JavaScript 对象入门</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Basics\">JavaScript 对象基础</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes\">对象原型</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_programming\">面向对象编程基本概念</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Classes_in_JavaScript\">JavaScript 中的类</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/JSON\">使用 JSON</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice\">实践对象构造</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features\">为“弹球”示例添加新功能</a></li></ol></details></li><li><details><summary>异步 JavaScript</summary><ol><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous\">异步 JavaScript</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing\">异步 JavaScript 简介</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous/Promises\">如何使用 Promise</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous/Implementing_a_promise-based_API\">如何实现基于 Promise 的 API</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing_workers\">workers 简介</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Asynchronous/Sequencing_animations\">序列动画</a></li></ol></details></li><li><details open=\"\"><summary>客户端 Web API</summary><ol><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs\">客户端 Web API</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction\">Web API 简介</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents\">操作文档</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data\">从服务器获取数据</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs\">第三方 API</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics\">绘图</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\">视频和音频 API</a></li><li><em><a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage\" aria-current=\"page\">客户端存储</a></em></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Forms\">Web 表单——与用户数据打交道</a></li><li><details><summary>Web 表单核心</summary><ol><li><a href=\"/zh-CN/docs/Learn/Forms\">Web 表单构建块</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Your_first_form\">创建我的第一个表单</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/How_to_structure_a_web_form\">如何构建 HTML 表单</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Basic_native_form_controls\">原生表单部件</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/HTML5_input_types\">HTML5 的输入(input)类型</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Other_form_controls\">其他表单控件</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Styling_web_forms\">样式化 Web 表单</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Advanced_form_styling\">表单样式化进阶</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/UI_pseudo-classes\">UI 伪类</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Form_validation\">表单数据校验</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Sending_and_retrieving_form_data\">发送表单数据</a></li></ol></details></li><li><details><summary>Web 表单进阶</summary><ol><li><a href=\"/zh-CN/docs/Learn/Forms/How_to_build_custom_form_controls\">如何构建自定义表单控件</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Sending_forms_through_JavaScript\">使用 JavaScript 发送表单</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/Property_compatibility_table_for_form_controls\">表单控件兼容性列表</a></li><li><a href=\"/zh-CN/docs/Learn/Forms/HTML_forms_in_legacy_browsers\">旧式浏览器中的 HTML 表单</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Accessibility\">无障碍——使每个人都能使用 Web</a></li><li><details><summary>无障碍指南</summary><ol><li><a href=\"/zh-CN/docs/Learn/Accessibility\">无障碍</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/What_is_accessibility\">什么是无障碍?</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/HTML\">HTML:无障碍的良好基础</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/CSS_and_JavaScript\">CSS 和 JavaScript 无障碍最佳实践</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics\">WAI-ARIA 基础</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/Multimedia\">多媒体无障碍</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/Mobile\">移动端无障碍</a></li><li><a href=\"/zh-CN/docs/Learn/Accessibility/Accessibility_troubleshooting\">测验:无障碍疑难解答</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Performance\">性能——使网站快速响应</a></li><li><details><summary>性能指南</summary><ol><li><a href=\"/zh-CN/docs/Learn/Performance\">Web 性能</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/why_web_performance\">Web 性能的重要性</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/What_is_web_performance\">什么是 web 性能?</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/Perceived_performance\">感知性能</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/Measuring_performance\">测量性能</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/Multimedia\">多媒体:图片</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/video\">多媒体:视频</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/JavaScript\">JavaScript 性能优化</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/HTML\">HTML 性能优化</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/CSS\">CSS 性能优化</a></li><li><a href=\"/zh-CN/docs/Learn/Performance/business_case_for_performance\">web 性能的商业案例</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/MathML\">MathML——使用 MathML 语言撰写数学表达式</a></li><li><details><summary>MathML 第一步</summary><ol><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps\">MathML 入门概述</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Getting_started\">MathML 使用入门</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Text_containers\">MathML 文本容器</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Fractions_and_roots\">MathML 分数和根号</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Scripts\">MathML 附加符号</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Tables\">MathML 表格</a></li><li><a href=\"/zh-CN/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas\">三个著名的数学公式</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/../Games\">游戏——开发 Web 游戏</a></li><li><details><summary>指南和基础教程</summary><ol><li><a href=\"/zh-CN/docs/Games/Introduction\">Web 游戏开发简介</a></li><li><a href=\"/zh-CN/docs/Games/Techniques\">游戏开发技术</a></li><li><a href=\"/zh-CN/docs/Games/Tutorials\">教程</a></li><li><a href=\"/zh-CN/docs/Games/Publishing_games\">发布游戏</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Tools_and_testing\">工具与测试</a></li><li><details><summary>客户端 web 开发工具</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools\">理解客户端 web 开发工具</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview\">客户端工具概述</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line\">命令行速成课</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management\">软件包管理基础</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain\">介绍完整的工具链</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment\">部署我们的应用</a></li></ol></details></li><li><details><summary>客户端框架介绍</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction\">客户端框架介绍</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features\">框架的主要特性</a></li></ol></details></li><li><details><summary>React</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started\">React 入门</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning\">开始我们的 React 待办清单</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components\">组件化我们的 React App</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state\">React interactivity: Events and state</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering\">React interactivity: Editing, filtering, conditional rendering</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility\">Accessibility in React</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources\">React resources</a></li></ol></details></li><li><details><summary>Ember</summary><ol><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started\">Getting started with Ember</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization\">Ember app structure and componentization</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state\">Ember interactivity: Events, classes and state</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer\">Ember Interactivity: Footer functionality, conditional rendering</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing\">Routing in Ember</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources\">Ember resources and troubleshooting</a></li></ol></details></li><li><details><summary>Vue</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started\">开始使用 Vue</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component\">创建第一个 Vue 组件</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists\">渲染 Vue 组件列表</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models\">使用 Vue event、method 和 model 添加一个新的 todo 表单</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling\">使用 CSS 为 Vue 组件添加样式</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties\">Vue 中的计算属性</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering\">Vue 中的条件渲染:编辑现有的待办事项</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management\">使用 Vue 模板引用进行焦点管理</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources\">Vue 资源</a></li></ol></details></li><li><details><summary>Svelte</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started\">Svelte 入门</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning\">开始编写我们的 Svelte 待办事项列表应用程序</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props\">Svelte 中的动态行为:变量和属性</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components\">将我们的 Svelte 应用组件化</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility\">Svelte 进阶:响应式、生命周期以及无障碍</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores\">使用 Svelte store</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript\">Svelte 对 TypeScript 的支持</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next\">部署以及下一步</a></li></ol></details></li><li><details><summary>Angular</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_getting_started\">Angular 入门</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_todo_list_beginning\">Beginning our Angular todo list app</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_styling\">Styling our Angular app</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_item_component\">Creating an item component</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_filtering\">Filtering our to-do items</a></li><li><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Angular_building\">Building Angular applications and further resources</a></li></ol></details></li><li><details><summary>Git 和 GitHub</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/GitHub\">Git 和 GitHub</a></li></ol></details></li><li><details><summary>跨浏览器测试</summary><ol><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing\">跨浏览器测试</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction\">跨浏览器测试介绍</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies\">测试的策略</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS\">处理常见的 HTML 和 CSS 问题</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript\">处理常见的 JavaScript 问题</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility\">解决常见的无障碍问题</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection\">实现特性检测</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing\">自动化测试简介</a></li><li><a href=\"/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment\">搭建自己的自动化测试环境</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Server-side\">服务端网页编程</a></li><li><details><summary>第一步</summary><ol><li><a href=\"/zh-CN/docs/Learn/Server-side/First_steps\">服务端网站编程的第一步</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Introduction\">服务端编程介绍</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview\">客户端服务端交互概述</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Web_frameworks\">服务端 web 框架</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Website_security\">站点安全</a></li></ol></details></li><li><details><summary>Django Web 框架(Python)</summary><ol><li><a href=\"/zh-CN/docs/Learn/Server-side/Django\">Django Web 框架 (python)</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Introduction\">Django 介绍</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/development_environment\">设置 Django 开发环境</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Tutorial_local_library_website\">Django Tutorial: The Local Library website</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/skeleton_website\">Django Tutorial Part 2: 创建网站的地基</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Models\">Django Tutorial Part 3: 使用模型</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Admin_site\">Django Tutorial Part 4: Django 管理员站点</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Home_page\">Django 教程 5:主页构建</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Generic_views\">Django 教程 6: 通用列表和详细信息视图</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Sessions\">Django 教程 7: 会话框架</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Authentication\">Django 教程 8:用户授权与许可</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Forms\">Django 教程 9: 使用表单</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Testing\">Django 教程 10: 测试 Django 网页应用</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/Deployment\">Django 教程 11:部署 Django 到生产环境</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/web_application_security\">Django Web 应用安全</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Django/django_assessment_blog\">评估:DIY Django 微博客</a></li></ol></details></li><li><details><summary>Express Web 框架(Node.js/JavaScript)</summary><ol><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs\">Express Web 框架(Node.js/JavaScript)</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction\">Express/Node 入门</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment\">设置 Node 开发环境</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website\">Express 教程:本地图书馆网站</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website\">Express 教程 2:创建站点框架</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose\">Express 教程 3:使用数据库 (Mongoose)</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes\">Express 教程 4:路由和控制器</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data\">Express 教程 5: 呈现图书馆数据</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms\">Express 教程 6: 使用表单</a></li><li><a href=\"/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment\">Express 教程 7: 部署到生产环境</a></li></ol></details></li><li class=\"section\"><a href=\"/zh-CN/docs/Learn/Common_questions\">更多资源</a></li><li><details><summary>常见问题</summary><ol><li><a href=\"/zh-CN/docs/Learn/Common_questions\">常见问题</a></li><li><a href=\"/zh-CN/docs/Learn/HTML/Howto\">使用 HTML 解决常见问题</a></li><li><a href=\"/zh-CN/docs/Learn/CSS/Howto\">解决常见的 CSS 问题</a></li><li><a href=\"/zh-CN/docs/Learn/JavaScript/Howto\">解决 JavaSctript 代码的常见问题</a></li><li><a href=\"/zh-CN/docs/Learn/Common_questions/Web_mechanics\">Web 机制</a></li><li><a href=\"/zh-CN/docs/Learn/Common_questions/Tools_and_setup\">工具和安装</a></li><li><a href=\"/zh-CN/docs/Learn/Common_questions/Design_and_accessibility\">设计与无障碍</a></li></ol></details></li></ol>","sidebarMacro":"LearnSidebar","body":[{"type":"prose","value":{"id":null,"title":null,"isH3":false,"content":"<ul class=\"prev-next\">\n <li><a class=\"button secondary\" href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\"><span class=\"button-wrap\"> 上一页 </span></a></li>\n <li><a class=\"button secondary\" href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs\"><span class=\"button-wrap\"> 概述:客户端 Web API</span></a></li>\n \n</ul>\n<p>现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。</p>\n<figure class=\"table-container\"><table class=\"learn-box standard-table\">\n <tbody>\n <tr>\n <th scope=\"row\">前提:</th>\n <td>JavaScript 基础(参见<a href=\"/zh-CN/docs/Learn/JavaScript/First_steps\">第一步</a>、<a href=\"/zh-CN/docs/Learn/JavaScript/Building_blocks\">构建代码块</a>、<a href=\"/zh-CN/docs/Learn/JavaScript/Objects\">JavaScript 对象</a>)、<a href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Introduction\">基础的客户端 API</a></td>\n </tr>\n <tr>\n <th scope=\"row\">目标:</th>\n <td>学习如何使用客户端存储 API 来存储应用数据。</td>\n </tr>\n </tbody>\n</table></figure>"}},{"type":"prose","value":{"id":"客户端存储?","title":"客户端存储?","isH3":false,"content":"<p>在其他的 MDN 学习中我们已经讨论过<a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99\">静态网站</a>和<a href=\"/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#%E5%8A%A8%E6%80%81%E7%BD%91%E7%AB%99\">动态网站</a>的区别。大多数现代的网站是动态的——它们在服务端使用各种类型的数据库(服务端存储)来存储数据,之后通过运行<a href=\"/zh-CN/docs/Learn/Server-side\">服务端</a>代码来查询需要的数据,把其数据插入到静态页面的模板中,并将生成的 HTML 提供给客户端,以在用户的浏览器中显示。</p>\n<p>客户端存储以相同的原理工作,但是在使用上有一些不同。它是由 JavaScript API 组成的因此允许你在客户端存储数据(比如在用户的机器上),而且可以在需要的时候查询相关的数据。这有很多明显的用处,比如:</p>\n<ul>\n <li>个性化网站偏好(比如显示一个用户选择的自定义微件、颜色主题或字体大小)。</li>\n <li>保存之前的站点行为(比如从先前的 session 中获取购物车中的内容,记住用户是否之前已经登陆过)。</li>\n <li>本地化保存数据和静态资源可以使一个站点更快(至少让资源变少)的下载,甚至可以在失去网络连接的情况下可用。</li>\n <li>将 Web 应用生成的文档保存在本地以供离线使用。</li>\n</ul>\n<p>通常客户端和服务端存储是结合在一起使用的。例如,你可以从数据库中下载一个由 Web 游戏或音乐播放器应用程序使用的音乐文件,将它们存储在客户端数据库中,并按需要播放它们。用户只需下载音乐文件一次——在随后的访问中,它们将从数据库中检索。</p>\n<div class=\"notecard note\">\n <p><strong>备注:</strong>使用客户端存储 API 可以存储的数据量是有限的(可能是每个 API 单独的和累积的总量);具体的限制取决于浏览器,也可能基于用户设置。参见<a href=\"/zh-CN/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria\">浏览器存储限制和清理标准</a>以了解更多信息。</p>\n</div>"}},{"type":"prose","value":{"id":"老做派:cookie","title":"老做派:cookie","isH3":true,"content":"<p>客户端存储的概念已经存在很长一段时间了。从 Web 的早期时代开始,网站就使用 <a href=\"/zh-CN/docs/Web/HTTP/Cookies\">cookie</a> 来存储信息,以在网站上提供个性化的用户体验。它们是 Web 中最早、最常用的客户端存储形式。</p>\n<p>如今,有更简单的机制可用于存储客户端数据,因此我们在本文中不会教授如何使用 cookie。然而,这并不意味着 cookie 在现代 Web 上完全没有用处——它们仍然被广泛用于存储与用户个性化和状态相关的数据,例如会话 ID 和访问令牌。有关 cookie 的更多信息,请参见我们的<a href=\"/zh-CN/docs/Web/HTTP/Cookies\">使用 HTTP cookie</a> 文章。</p>"}},{"type":"prose","value":{"id":"新流派:web_存储和_indexeddb","title":"新流派:Web 存储和 IndexedDB","isH3":true,"content":"<p>我们在上面所提到的“更简单”的特性如下:</p>\n<ul>\n <li><a href=\"/zh-CN/docs/Web/API/Web_Storage_API\">Web 存储 API</a> 提供了一种非常简单的语法,用于存储和检索较小的、由名称和相应值组成的数据项。当你只需要存储一些简单的数据时,比如用户的名字、用户是否登录、屏幕背景使用了什么颜色等等,这是非常有用的。</li>\n <li><a href=\"/zh-CN/docs/Web/API/IndexedDB_API\">IndexedDB API</a> 为浏览器提供了一个完整的数据库系统来存储复杂的数据。这可以用于存储从完整的用户记录到甚至是复杂的数据类型,如音频或视频文件。</li>\n</ul>\n<p>你将在下面了解更多关于这些 API 的信息。</p>"}},{"type":"prose","value":{"id":"cache_api","title":"Cache API","isH3":true,"content":"<p><a href=\"/zh-CN/docs/Web/API/Cache\"><code>Cache</code></a> API 是为存储特定 HTTP 请求的响应文件而设计的,它对于像存储离线网站文件这样的事情非常有用,这样网站就可以在没有网络连接的情况下使用。缓存通常与 <a href=\"/zh-CN/docs/Web/API/Service_Worker_API\">Service Worker API</a> 组合使用,尽管不一定非要这么做。</p>\n<p>Cache 和 Service Worker 的使用是一个高级主题,我们不会在本文中详细讨论它,尽管我们将在下面的<a href=\"#%E7%A6%BB%E7%BA%BF%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8\">离线文件存储</a>小节中展示一个简单的例子。</p>"}},{"type":"prose","value":{"id":"存储简单数据——web_存储","title":"存储简单数据——Web 存储","isH3":false,"content":"<p><a href=\"/zh-CN/docs/Web/API/Web_Storage_API\">Web 存储 API</a> 非常容易使用——你只需存储简单的键名/键值对数据(限制为字符串、数字等类型)并在需要的时候检索其值。</p>"}},{"type":"prose","value":{"id":"基本语法","title":"基本语法","isH3":true,"content":"<p>让我们来告诉你怎么做:</p>\n<ol>\n <li>\n <p>第一步,访问 GitHub 上的 <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html\" class=\"external\" target=\"_blank\">Web 存储空白模板</a>(在新标签页打开它)。</p>\n </li>\n <li>\n <p>打开你浏览器开发者工具的 JavaScript 控制台。</p>\n </li>\n <li>\n <p>你所有的 Web 存储数据都包含在浏览器内两个类似于对象的结构中:<a href=\"/zh-CN/docs/Web/API/Window/sessionStorage\" title=\"sessionStorage\"><code>sessionStorage</code></a> 和 <a href=\"/zh-CN/docs/Web/API/Window/localStorage\" title=\"localStorage\"><code>localStorage</code></a>。第一种方法,只要浏览器开着,数据就会一直保存(关闭浏览器时数据会丢失),而第二种会一直保存数据,甚至到浏览器关闭又开启后也是这样。我们将在本文中使用第二种方法,因为它通常更有用。</p>\n <p><a href=\"/zh-CN/docs/Web/API/Storage/setItem\"><code>Storage.setItem()</code></a> 方法允许你在存储中保存一个数据项——它接受两个参数:数据项的名字及其值。试着把它输入到你的 JavaScript 控制台(如果你愿意的话,可以把它的值改为你自己的名字!)</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.setItem(\"name\", \"Chris\");\n</code></pre></div>\n </li>\n <li>\n <p><a href=\"/zh-CN/docs/Web/API/Storage/getItem\"><code>Storage.getItem()</code></a> 方法接受一个参数——你想要检索的数据项的名称——并返回数据项的值。现在将这些代码输入到你的 JavaScript 控制台:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>let myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>在输入第二行时,你应该会看到 <code>myName</code> 变量现在包含 <code>name</code> 数据项的值。</p>\n </li>\n <li>\n <p><a href=\"/zh-CN/docs/Web/API/Storage/removeItem\"><code>Storage.removeItem()</code></a> 方法接受一个参数——你想要删除的数据项的名称——并从 Web 存储中删除该数据项。在你的 JavaScript 控制台中输入以下几行:</p>\n </li>\n</ol>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.removeItem(\"name\");\nmyName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n<p>第三行现在应该返回 <code>null</code>——<code>name</code> 项已经不存在于 Web 存储中。</p>"}},{"type":"prose","value":{"id":"数据会一直存在!","title":"数据会一直存在!","isH3":true,"content":"<p>Web 存储的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对 <code>localStorage</code> 而言)。让我们来看看这个:</p>\n<ol>\n <li>\n <p>再次打开我们的 Web 存储空白模板,但是这次你要在不同的浏览器中打开这个教程!这样可以更容易处理。</p>\n </li>\n <li>\n <p>在浏览器的 JavaScript 控制台中输入这几行:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>localStorage.setItem(\"name\", \"Chris\");\nlet myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>你应该看到 name 数据项返回。</p>\n </li>\n <li>\n <p>现在关掉浏览器再把它打开。</p>\n </li>\n <li>\n <p>再次输入下面几行:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>let myName = localStorage.getItem(\"name\");\nmyName;\n</code></pre></div>\n <p>你应该看到,尽管浏览器已经关闭,然后再次打开,但仍然可以使用该值。</p>\n </li>\n</ol>"}},{"type":"prose","value":{"id":"为每个域名分离储存","title":"为每个域名分离储存","isH3":true,"content":"<p>每个域都有一个单独的数据存储区(每个单独的网址都在浏览器中加载)。你会看到,如果你加载两个网站(例如 google.com 和 amazon.com)并尝试将某个项目存储在一个网站上,该数据项将无法从另一个网站获取。</p>\n<p>这是有道理的——你可以想象如果网站能够查看彼此的数据,就会出现安全问题!</p>"}},{"type":"prose","value":{"id":"更复杂的例子","title":"更复杂的例子","isH3":true,"content":"<p>让我们通过编写一个简单的工作示例来应用这些新发现的知识,让你了解如何使用网络存储。我们的示例将允许你输入一个名称,然后该页面将刷新,以提供个性化问候。这种状态也会页面/浏览器重新加载期间保持,因为这个名称存储在 Web 存储中。</p>\n<p>你可以在 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">personal-greeting.html</a> 中找到示例 HTML——这包含一个具有标题、内容和页脚,以及用于输入你的姓名的表单的简单网站。</p>\n<p>\n <img src=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/web-storage-demo.png\" alt=\"一张网站的截图,包含了页头、内容和页脚部分。页头的左侧有一段欢迎文本,右侧有一个标记为“忘记”的按钮。内容部分包括一个标题,接着是两段占位文本。页脚显示“版权归任何人所有。随意使用代码。”\" width=\"1024\" height=\"550\" loading=\"lazy\">\n</p>\n<p>让我们来构建示例,以便了解它的工作原理。</p>\n<ol>\n <li>\n <p>首先,在你的计算机上的新目录中创建一个 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">personal-greeting.html</a> 文件的副本。</p>\n </li>\n <li>\n <p>接下来,请注意我们的 HTML 如何引用一个名为 <code>index.js</code> 的 JavaScript 文件(就像 <code>&lt;script src=\"index.js\" defer&gt;&lt;/script&gt;</code>)。我们需要创建它并将 JavaScript 代码写入其中。在与 HTML 文件相同的目录中创建一个 <code>index.js</code> 文件。</p>\n </li>\n <li>\n <p>我们首先创建对所有需要在此示例中操作的 HTML 特性的引用——我们将它们全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。将以下几行添加到你的 JavaScript 文件中:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 创建所需的常量\nconst rememberDiv = document.querySelector(\".remember\");\nconst forgetDiv = document.querySelector(\".forget\");\nconst form = document.querySelector(\"form\");\nconst nameInput = document.querySelector(\"#entername\");\nconst submitBtn = document.querySelector(\"#submitname\");\nconst forgetBtn = document.querySelector(\"#forgetname\");\n\nconst h1 = document.querySelector(\"h1\");\nconst personalGreeting = document.querySelector(\".personal-greeting\");\n</code></pre></div>\n </li>\n <li>\n <p>接下来,我们需要包含一个小小的事件监听器,以在按下提交按钮时阻止实际的提交表单动作自身,因为这不是我们想要的行为。在你之前的代码下添加此代码段:在你之前的代码后添加这段代码:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 当按钮按下时阻止表单提交\nform.addEventListener(\"submit\", (e) =&gt; e.preventDefault());\n</code></pre></div>\n </li>\n <li>\n <p>现在我们需要添加一个事件监听器,当单击“Say hello”按钮时,它的处理函数将会运行。这些注释详细解释了每一处都做了什么,但实际上我们在这里获取用户输入到文本输入框中的名字并使用 <code>setItem()</code> 将它保存在网络存储中,然后运行一个名为 <code>nameDisplayCheck()</code> 的函数来处理实际的网站文本的更新。将此添加到代码的底部:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 当点击“Say hello”按钮时运行函数\nsubmitBtn.addEventListener(\"click\", () =&gt; {\n // 将输入的名字存储到网页存储中\n localStorage.setItem(\"name\", nameInput.value);\n // 运行 nameDisplayCheck() 来处理显示个性化问候语和更新表单显示\n nameDisplayCheck();\n});\n</code></pre></div>\n </li>\n <li>\n <p>此时,我们还需要一个事件处理器,以便在单击“Forget”按钮时运行一个函数——且仅在单击“Say hello”按钮(两种表单状态来回切换)后才显示。在这个函数中,我们使用 <code>removeItem()</code> 从 Web 存储中删除项目 <code>name</code>,然后再次运行 <code>nameDisplayCheck()</code> 以更新显示。将其添加到底部:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 当点击“Forget”按钮时运行函数\nforgetBtn.addEventListener(\"click\", () =&gt; {\n // 从网页存储中移除存储的名字\n localStorage.removeItem(\"name\");\n // 运行 nameDisplayCheck() 来重新显示通用问候语并更新表单显示\n nameDisplayCheck();\n});\n</code></pre></div>\n </li>\n <li>\n <p>现在是时候定义 <code>nameDisplayCheck()</code> 函数本身了。在这里,我们通过使用 <code>localStorage.getItem('name')</code> 作为测试条件来检查 name 数据项是否已经存储在 Web 存储中。如果它已被存储,则该调用的返回值为 <code>true</code>;果没有,它会是 <code>false</code>。如果是 <code>true</code>,我们会显示个性化问候语,显示表格的“forget”部分,并隐藏表格的“Say hello”部分。如果是 <code>false</code>,我们会显示一个通用问候语,并做相反的事。再次将下面的代码添到底部:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 nameDisplayCheck() 函数\nfunction nameDisplayCheck() {\n // 检查 'name' 数据项是否存储在网页存储中\n if (localStorage.getItem(\"name\")) {\n // 如果存在,显示个性化问候语\n const name = localStorage.getItem(\"name\");\n h1.textContent = `欢迎,${name}`;\n personalGreeting.textContent = `欢迎来到我们的网站,${name}!希望您在这里玩得开心。`;\n // 隐藏表单中的 'remember' 部分,显示 'forget' 部分\n forgetDiv.style.display = \"block\";\n rememberDiv.style.display = \"none\";\n } else {\n // 如果不存在,显示通用问候语\n h1.textContent = \"欢迎来到我们的网站\";\n personalGreeting.textContent =\n \"欢迎来到我们的网站。希望您在这里玩得开心。\";\n // 隐藏表单中的 'forget' 部分,显示 'remember' 部分\n forgetDiv.style.display = \"none\";\n rememberDiv.style.display = \"block\";\n }\n}\n</code></pre></div>\n </li>\n <li>\n <p>最后但同样重要的是,我们需要在每次加载页面时运行 <code>nameDisplayCheck()</code> 函数。如果我们不这样做,那么个性化问候不会在页面重新加载后保持。将以下代码添加到代码的底部:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>nameDisplayCheck();\n</code></pre></div>\n </li>\n</ol>\n<p>你的例子完成了——做得好!现在剩下的就是保存你的代码并在浏览器中测试你的 HTML 页面。你可以在这里看到我们的<a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html\" class=\"external\" target=\"_blank\">完成版本并在线运行</a>。</p>\n<div class=\"notecard note\">\n <p><strong>备注:</strong>在<a href=\"/zh-CN/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\">使用 Web 存储 API</a> 中还有一个稍微复杂点儿的示例。</p>\n</div>\n<div class=\"notecard note\">\n <p><strong>备注:</strong>在完成版本的源代码中,<code>&lt;script src=\"index.js\" defer&gt;&lt;/script&gt;</code> 一行里,<code>defer</code> 属性指明在页面加载完成之前,<a href=\"/zh-CN/docs/Web/HTML/Element/script\"><code>&lt;script&gt;</code></a> 元素的内容不会执行。</p>\n</div>"}},{"type":"prose","value":{"id":"存储复杂数据——indexeddb","title":"存储复杂数据——IndexedDB","isH3":false,"content":"<p><a href=\"/zh-CN/docs/Web/API/IndexedDB_API\">IndexedDB API</a>(有时简称 IDB)是可以在浏览器中访问的一个完整的数据库系统,在这里,你可以存储复杂的关系数据。其种类不限于像字符串和数字这样的简单值。你可以在一个 IndexedDB 中存储视频,图像和许多其他的内容。</p>\n<p>IndexedDB API 允许你创建一个数据库,然后在该数据库中创建对象存储。对象存储类似于关系型数据库中的表,每个对象存储可以包含多个对象。要了解有关 IndexedDB API 的更多信息,请参见<a href=\"/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB\">使用 IndexedDB</a>。</p>\n<p>但是,这确实是有代价的:使用 IndexedDB 要比 Web 存储 API 复杂得多。在本节中,我们仅仅只能浅尝辄止地一提它的能力,不过我们会给你足够基础知识以帮助你开始。</p>"}},{"type":"prose","value":{"id":"通过一个笔记存储示例演示","title":"通过一个笔记存储示例演示","isH3":true,"content":"<p>在这里,我们将向你介绍一个示例,该示例允许你在浏览器中存储笔记并随时查看和删除它们,在我们进行时,我们将解释 IDB 的最基本部分并让你自己构建笔记。</p>\n<p>这个应用看起来像这样:</p>\n<p>\n <img src=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/idb-demo.png\" alt=\"IndexDB 笔记演示的截图包含四个部分。第一部分是页头。第二部分列出了所有已创建的笔记,包括两条笔记,每条笔记都有一个删除按钮。第三部分是一个表单,包含两个输入字段用于“笔记标题”和“笔记内容”,以及一个标记为“创建新笔记”的按钮。底部部分的页脚显示“版权归任何人所有。随意使用代码。”\" width=\"803\" height=\"736\" loading=\"lazy\">\n</p>\n<p>每个笔记都有一个标题和一些正文,每个都可以单独编辑。我们将在下面通过的 JavaScript 代码提供详细的笔记,以帮助你了解正在发生的事情。</p>"}},{"type":"prose","value":{"id":"开始","title":"开始","isH3":true,"content":"<p>1、首先,将 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html\" class=\"external\" target=\"_blank\"><code>index.html</code></a>、<a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css\" class=\"external\" target=\"_blank\"><code>style.css</code></a> 和 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js\" class=\"external\" target=\"_blank\"><code>index-start.js</code></a> 文件的本地副本放入本地计算机上的新目录中。</p>\n<p>2、浏览这些文件。你将看到 HTML 非常简单:具有页眉和页脚的网站,以及包含显示笔记的位置的主内容区域,以及用于在数据库中输入新笔记的表单。CSS 提供了一些简单的样式,使其更清晰。JavaScript 文件包含五个声明的常量,其中包含对将显示笔记的 <a href=\"/zh-CN/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> 元素的引用、标题和正文 <a href=\"/zh-CN/docs/Web/HTML/Element/input\"><code>&lt;input&gt;</code></a> 元素、<a href=\"/zh-CN/docs/Web/HTML/Element/form\"><code>&lt;form&gt;</code></a> 本身,以及 <a href=\"/zh-CN/docs/Web/HTML/Element/button\"><code>&lt;button&gt;</code></a>。</p>\n<p>3、将你的 JavaScript 文件重命名为 <code>index.js</code>。你现在可以开始向其添加代码了。</p>"}},{"type":"prose","value":{"id":"数据库初始设置","title":"数据库初始设置","isH3":true,"content":"<p>现在让我们来看看为了建立数据库必须首先要做什么。</p>\n<ol>\n <li>\n <p>在常量声明下,加入这几行:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 创建一个 db 对象的实例,用于存储打开的数据库\nlet db;\n</code></pre></div>\n <p>这里我们声明了一个叫 <code>db</code> 的变量——这将在之后被用来存储一个代表数据库的对象。我们将在几个地方使用它,所以我们为了方便使用而在这里把它声明为全局的。</p>\n </li>\n <li>\n <p>接着,添加如下代码:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 打开我们的数据库;如果数据库不存在,将会创建它\n// (请参见下面的 upgradeneeded 处理器)\nconst openRequest = window.indexedDB.open(\"notes_db\", 1);\n</code></pre></div>\n <p>这一行代码创建了一个请求,用于打开名为 <code>notes_db</code> 的版本 <code>1</code> 的数据库。如果该数据库尚不存在,它将由后续代码创建。你会在 IndexedDB 中经常看到这种请求模式。数据库操作需要时间。你不希望在等待结果时使浏览器卡死,因此数据库操作是<a href=\"/zh-CN/docs/Glossary/Asynchronous\">异步</a>的,意味着操作不会立即发生,而是在未来的某个时间发生,并且你会在操作完成时收到通知。</p>\n <p>在 IndexedDB 中处理这一点的方法是创建一个请求对象(可以随意命名——我们在这里称之为 <code>openRequest</code>,这样它的用途就很明显)。然后,你可以使用事件处理器来运行代码,当请求完成、失败等时,你可以看到下面的用法。</p>\n <div class=\"notecard note\">\n <p><strong>备注:</strong>版本号很重要。如果你想升级数据库(例如,通过更改表结构),你需要再次运行代码,增加版本号,并在 <code>upgradeneeded</code> 处理器中指定不同的模式等。我们在本教程中不会涉及数据库的升级。</p>\n </div>\n </li>\n <li>\n <p>现在,在你之前添加的代码下面添加以下事件处理器:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 错误处理器表示数据库未成功打开\nopenRequest.addEventListener(\"error\", () =&gt; console.error(\"数据库打开失败\"));\n\n// 成功处理器表示数据库成功打开\nopenRequest.addEventListener(\"success\", () =&gt; {\n console.log(\"数据库成功打开\");\n\n // 将打开的数据库对象存储在 db 变量中。下面会多次使用\n db = openRequest.result;\n\n // 运行 displayData() 函数以显示已存在于 IDB 中的笔记\n displayData();\n});\n</code></pre></div>\n <p><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBRequest/error_event\"><code>error</code></a> 事件处理器会在系统返回请求失败的消息时运行。这允许你对这个问题做出响应。在我们的示例中,我们只是将一条消息打印到 JavaScript 控制台。</p>\n <p><a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBRequest/success_event\"><code>success</code></a> 事件处理器会在请求成功返回时运行,意味着数据库已成功打开。如果是这种情况,表示打开的数据库的对象会在 <a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBRequest/result\"><code>openRequest.result</code></a> 属性中提供,允许我们操作数据库。我们将其存储在之前创建的 <code>db</code> 变量中以供后续使用。我们还会运行一个名为 <code>displayData()</code> 的函数,用于在 HTML 中的 <code>ul</code> 元素内显示数据库中的数据。我们现在运行它,以便在页面加载时立即显示数据库中已经存在的笔记。你会在稍后看到 <code>displayData()</code> 的定义。</p>\n </li>\n <li>\n <p>最后,为了完成这一部分,我们将添加可能是设置数据库时最重要的事件处理器:<a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event\"><code>upgradeneeded</code></a>。如果数据库尚未设置,或数据库以比现有存储的数据库更大的版本号打开(进行升级时),该处理器会运行。在你之前的处理器下面添加以下代码:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 如果尚未设置数据库表,则进行设置\nopenRequest.addEventListener(\"upgradeneeded\", (e) =&gt; {\n // 获取已打开的数据库的引用\n db = e.target.result;\n\n // 在我们的数据库中创建一个用于存储笔记和自增键的 objectStore\n // objectStore 类似于关系数据库中的“表”\n const objectStore = db.createObjectStore(\"notes_os\", {\n keyPath: \"id\",\n autoIncrement: true,\n });\n\n // 定义 objectStore 将包含的数据项\n objectStore.createIndex(\"title\", \"title\", { unique: false });\n objectStore.createIndex(\"body\", \"body\", { unique: false });\n\n console.log(\"数据库设置完成\");\n});\n</code></pre></div>\n <p>在这里我们定义了数据库的模式(结构);即它包含的列(或字段)集合。首先,我们从事件的目标 (<code>e.target.result</code>) 的 <code>result</code> 属性中获取现有数据库的引用,这就是 <code>request</code> 对象。这等同于在<code>成功</code>事件处理器中的 <code>db = openRequest.result;</code>,但我们需要在这里单独进行,因为 <code>upgradeneeded</code> 事件处理器(如果需要的话)会在<code>成功</code>事件处理器之前运行,这意味着如果我们不这样做,<code>db</code> 值将不可用。</p>\n <p>然后,我们使用 <a href=\"/zh-CN/docs/Web/API/IDBDatabase/createObjectStore\"><code>IDBDatabase.createObjectStore()</code></a> 在打开的数据库中创建一个名为 <code>notes_os</code> 的新 objectStore。这相当于传统数据库系统中的一个表。我们给它指定了名称 <code>notes</code>,并指定了一个 <code>autoIncrement</code> 键字段 <code>id</code>——在每条新记录中,这个字段会自动分配递增的值——开发者不需要显式设置它。作为键,<code>id</code> 字段将用于唯一标识记录,例如在删除或显示记录时。</p>\n <p>我们还使用 <a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBObjectStore/createIndex\"><code>IDBObjectStore.createIndex()</code></a> 方法创建了两个其他索引(字段):<code>title</code>(包含每条笔记的标题)和 <code>body</code>(包含笔记的正文内容)。</p>\n <p>设置好这个数据库模式后,当我们开始向数据库中添加记录时,每条记录将表示为类似于以下格式的对象:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">json</span></div><pre class=\"brush: json notranslate\"><code>{\n \"title\": \"Buy milk\",\n \"body\": \"Need both cows milk and soy.\",\n \"id\": 8\n}\n</code></pre></div>\n </li>\n</ol>"}},{"type":"prose","value":{"id":"添加数据到数据库","title":"添加数据到数据库","isH3":true,"content":"<p>现在让我们看一下如何将记录添加到数据库中。这将使用我们页面上的表单完成。</p>\n<p>在你之前的事件处理器下面,添加以下一行,它设置了一个 <code>submit</code> 事件处理器,当表单被提交时(当提交 <a href=\"/zh-CN/docs/Web/HTML/Element/button\"><code>&lt;button&gt;</code></a> 元素被按下导致表单成功提交),运行一个叫做 <code>addData()</code> 的函数:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 创建一个提交事件处理器,当表单提交时运行 addData() 函数\nform.addEventListener(\"submit\", addData);\n</code></pre></div>\n<p>现在让我们定义 <code>addData()</code> 函数。在你之前的代码下面添加以下内容:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 addData() 函数\nfunction addData(e) {\n // 阻止默认行为——我们不希望表单以传统方式提交\n e.preventDefault();\n\n // 获取输入字段中输入的值,并将它们存储在一个对象中,准备插入到数据库中\n const newItem = { title: titleInput.value, body: bodyInput.value };\n\n // 打开一个读/写事务,准备添加数据\n const transaction = db.transaction([\"notes_os\"], \"readwrite\");\n\n // 调用已添加到数据库中的 objectStore\n const objectStore = transaction.objectStore(\"notes_os\");\n\n // 发起请求,将我们的 newItem 对象添加到 objectStore 中\n const addRequest = objectStore.add(newItem);\n\n addRequest.addEventListener(\"success\", () =&gt; {\n // 清空表单,为添加下一个条目做好准备\n titleInput.value = \"\";\n bodyInput.value = \"\";\n });\n\n // 在事务完成时报告成功,当所有操作完成后\n transaction.addEventListener(\"complete\", () =&gt; {\n console.log(\"事务完成:数据库修改结束。\");\n\n // 通过再次运行 displayData() 来更新数据的显示,以显示新添加的条目\n displayData();\n });\n\n transaction.addEventListener(\"error\", () =&gt;\n console.log(\"事务未成功打开,出现错误\"),\n );\n}\n</code></pre></div>\n<p>这很复杂;要打破它,我们:</p>\n<ul>\n <li>在事件对象上运行 <a href=\"/zh-CN/docs/Web/API/Event/preventDefault\"><code>Event.preventDefault()</code></a> 以停止以传统方式实际提交的表单(这将导致页面刷新并破坏体验)。</li>\n <li>创建一个表示要输入数据库的记录的对象,并使用表单输入中的值填充它。请注意,我们不必明确包含一个 <code>id</code> 值——正如我们提前详细说明的那样,这是自动填充的。</li>\n <li>使用 <a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBDatabase/transaction\"><code>IDBDatabase.transaction()</code></a> 方法打开 <code>notes</code> 对象存储的 <code>readwrite</code> 事务。此事务对象允许我们访问对象存储,以便我们可以对其执行某些操作,例如添加新记录。</li>\n <li>使用 <a class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\" href=\"/en-US/docs/Web/API/IDBTransaction/objectStore\"><code>IDBTransaction.objectStore()</code></a> 方法访问对象库,将结果保存在 <code>objectStore</code> 变量中。</li>\n <li>使用 <a href=\"/zh-CN/docs/Web/API/IDBObjectStore/add\"><code>IDBObjectStore.add()</code></a> 添加新记录到数据库。这创建了一个请求对象,与我们之前看到的方式相同。</li>\n <li>在生命周期的关键点为 <code>request</code> 以及 <code>transaction</code> 对象添加事件处理器以运行代码。请求成功后,我们会清除表单输入,以便输入下一个笔记。交易完成后,我们再次运行 <code>displayData()</code> 函数以更新页面上的笔记显示。</li>\n</ul>"}},{"type":"prose","value":{"id":"显示数据","title":"显示数据","isH3":true,"content":"<p>我们已经在代码中引用了 <code>displayData()</code> 两次,所以我们可能更好地定义它。将其添加到你的代码中,位于上一个函数定义之下:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 displayData() 函数\nfunction displayData() {\n // 每次更新显示时,我们都清空列表元素的内容\n // 如果不这样做,每次添加新笔记时列表中会出现重复项\n while (list.firstChild) {\n list.removeChild(list.firstChild);\n }\n\n // 打开我们的对象存储,然后获取游标——它会迭代存储中的所有数据项\n const objectStore = db.transaction(\"notes_os\").objectStore(\"notes_os\");\n objectStore.openCursor().addEventListener(\"success\", (e) =&gt; {\n // 获取游标的引用\n const cursor = e.target.result;\n\n // 如果还有数据项需要迭代,则继续运行此代码\n if (cursor) {\n // 创建一个列表项、h3 和 p 元素,用于在显示数据项时放置它们\n // 构建 HTML 片段,并将其附加到列表中\n const listItem = document.createElement(\"li\");\n const h3 = document.createElement(\"h3\");\n const para = document.createElement(\"p\");\n\n listItem.appendChild(h3);\n listItem.appendChild(para);\n list.appendChild(listItem);\n\n // 将游标中的数据放入 h3 和 para 中\n h3.textContent = cursor.value.title;\n para.textContent = cursor.value.body;\n\n // 将数据项的 ID 存储在 listItem 的一个属性中,以便我们知道\n // 这项数据对应哪个条目。这在稍后删除条目时会很有用\n listItem.setAttribute(\"data-note-id\", cursor.value.id);\n\n // 创建一个按钮,并将其放置在每个 listItem 中\n const deleteBtn = document.createElement(\"button\");\n listItem.appendChild(deleteBtn);\n deleteBtn.textContent = \"删除\";\n\n // 设置事件处理器,当按钮被点击时,运行 deleteItem() 函数\n deleteBtn.addEventListener(\"click\", deleteItem);\n\n // 迭代到游标中的下一个项\n cursor.continue();\n } else {\n // 如果列表为空,则显示“没有存储的笔记”消息\n if (!list.firstChild) {\n const listItem = document.createElement(\"li\");\n listItem.textContent = \"没有存储的笔记。\";\n list.appendChild(listItem);\n }\n // 如果没有更多的游标项需要迭代,说明所有笔记都已显示\n console.log(\"所有笔记已显示\");\n }\n });\n}\n</code></pre></div>\n<p>再次,让我们打破这个:</p>\n<ul>\n <li>首先,我们清空 <a href=\"/zh-CN/docs/Web/HTML/Element/ul\"><code>&lt;ul&gt;</code></a> 元素的内容,然后填充更新的内容。如果你不这样做,那么每次更新时都会添加大量重复内容。</li>\n <li>接下来,我们 <code>notes</code> 使用 <a href=\"/en-US/docs/Web/API/IDBDatabase/transaction\" class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\"><code>IDBDatabase.transaction()</code></a> 和 <a href=\"/en-US/docs/Web/API/IDBTransaction/objectStore\" class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\"><code>IDBTransaction.objectStore()</code></a> 我们一样得到对象存储的引用 <code>addData()</code>,除了这里我们将它们链接在一行中。</li>\n <li>下一步是使用 <a href=\"/zh-CN/docs/Web/API/IDBObjectStore/openCursor\"><code>IDBObjectStore.openCursor()</code></a> 方法打开对游标的请求——这是一个可用于迭代对象存储中的记录的构造。我们将一个 <code>onsuccess</code> 处理器链接到该行的末尾以使代码更简洁——当成功返回游标时,运行处理器。</li>\n <li>我们 <a href=\"/zh-CN/docs/Web/API/IDBCursor\"><code>IDBCursor</code></a> 使用 let 获取对游标本身(对象)的引用 <code>cursor = e.target.result</code>。</li>\n <li>接下来,我们检查光标是否包含来自数据存储区(<code>if(cursor){ ... }</code>)的记录——如果包含,我们创建一个 DOM 片段,用记录中的数据填充它,然后将其插入页面(<code>&lt;ul&gt;</code> 元素内部)。我们还包括一个删除按钮,当单击该按钮时,将通过运行 <code>deleteItem()</code> 函数删除该笔记(我们将在下一节中查看)。</li>\n <li>在 <code>if</code> 块结束时,我们使用该 <a href=\"/en-US/docs/Web/API/IDBCursor/continue\" class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\"><code>IDBCursor.continue()</code></a> 方法将光标前进到数据存储区中的下一条记录,然后<code>if</code>再次运行块的内容。如果有另一个要迭代的记录,这会导致它被插入到页面中,然后 <code>continue()</code> 再次运行,依此类推。</li>\n <li>当没有更多记录要迭代时,<code>cursor</code> 将返回 <code>undefined</code>,因此 <code>else</code> 块将运行(而不是 <code>if</code> 块)。此块检查是否有任何笔记被插入 <code>&lt;ul&gt;</code>——如果没有,它会插入一条消息,说没有存储的笔记。</li>\n</ul>"}},{"type":"prose","value":{"id":"删除一条笔记","title":"删除一条笔记","isH3":true,"content":"<p>如上所述,当按下笔记的删除按钮时,笔记将被删除。这是通过 <code>deleteItem()</code> 函数实现的,如下所示:</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 deleteItem() 函数\nfunction deleteItem(e) {\n // 获取要删除的任务的 ID。我们需要将其转换为数字,因为在 IDB 中使用时\n // IDB 键值对对类型敏感。\n const noteId = Number(e.target.parentNode.getAttribute(\"data-note-id\"));\n\n // 打开一个数据库事务并删除任务,使用我们上面检索到的 ID 查找它\n const transaction = db.transaction([\"notes_os\"], \"readwrite\");\n const objectStore = transaction.objectStore(\"notes_os\");\n const deleteRequest = objectStore.delete(noteId);\n\n // 报告数据项已被删除\n transaction.addEventListener(\"complete\", () =&gt; {\n // 删除按钮的父元素\n // 也就是列表项,使其不再显示\n e.target.parentNode.parentNode.removeChild(e.target.parentNode);\n console.log(`笔记 ${noteId} 已删除。`);\n\n // 如果列表为空,则显示“没有存储的笔记”消息\n if (!list.firstChild) {\n const listItem = document.createElement(\"li\");\n listItem.textContent = \"没有存储的笔记。\";\n list.appendChild(listItem);\n }\n });\n}\n</code></pre></div>\n<ul>\n <li>第一部分可以使用一些解释——我们检索要删除 <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> 的记录的 ID——回想一下记录的 ID 是在第一次显示时保存在 <code>data-note-id</code> 属性中的 <code>&lt;li&gt;</code>。但是,我们需要通过全局内置的 <a href=\"/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number\"><code>Number()</code></a> 对象传递属性,因为它当前是一个字符串,否则将无法被数据库识别。</li>\n <li>然后,我们使用我们之前看到的相同模式获取对对象存储的引用,并使用该 <a href=\"/en-US/docs/Web/API/IDBObjectStore/delete\" class=\"only-in-en-us\" title=\"此页面目前仅提供英文版本\"><code>IDBObjectStore.delete()</code></a> 方法从数据库中删除记录,并将 ID 传递给它。</li>\n <li>当数据库事务完成后,我们从 DOM 中删除笔记的 <code>&lt;li&gt;</code>,然后再次检查以查看 <code>&lt;ul&gt;</code> 是否为空,并根据需要插入笔记。</li>\n</ul>\n<p>就是这样了!你的例子现在应该有效。</p>\n<p>如果你遇到问题,请随时<a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/\" class=\"external\" target=\"_blank\">查看我们的实例</a>(请参阅<a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.js\" class=\"external\" target=\"_blank\">源代码</a>)。</p>"}},{"type":"prose","value":{"id":"通过_indexeddb_存储复杂数据","title":"通过 IndexedDB 存储复杂数据","isH3":true,"content":"<p>如上所述,IndexedDB 可用于存储不仅仅是简单的文本字符串。你可以存储任何你想要的东西,包括复杂的对象,如视频或图像 blob。并且它比任何其他类型的数据更难实现。</p>\n<p>为了演示如何操作,我们编写了另一个名为 <a href=\"https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/indexeddb/video-store\" class=\"external\" target=\"_blank\">IndexedDB 视频存储</a>的(也可<a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/\" class=\"external\" target=\"_blank\">在线运行</a>)。首次运行示例时,它会从网络下载所有视频,将它们存储在 IndexedDB 数据库中,然后在 UI 内部 <a href=\"/zh-CN/docs/Web/HTML/Element/video\"><code>&lt;video&gt;</code></a> 元素中显示视频。第二次运行它时,它会在数据库中找到视频并从那里获取它们而不是显示它们——这使得后续加载更快,占用空间更少。</p>\n<p>让我们来看看这个例子中最有趣的部分。我们不会全部看——它的很多内容与上一个示例类似,代码注释得很好。</p>\n<ol>\n <li>\n <p>对于这个简单的例子,我们已经存储了视频的名称以获取数组对象:</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>const videos = [\n { name: \"crystal\" },\n { name: \"elf\" },\n { name: \"frog\" },\n { name: \"monster\" },\n { name: \"pig\" },\n { name: \"rabbit\" },\n];\n</code></pre></div>\n </li>\n <li>\n <p>首先,一旦数据库成功打开,我们就运行 <code>init()</code> 函数。这会遍历不同的视频名称,尝试加载由 <code>videos</code> 数据库中的每个名称标识的记录。</p>\n <p>如果在数据库中找到每个视频(通过查看 <code>request.result</code> 评估是否容易检查 <code>true</code>——如果记录不存在,那么 <code>undefined</code>),视频文件(存储为 blob)和视频名称将直接传递给 <code>displayVideo()</code> 函数以放置它们在用户界面中。如果没有,视频名称将传递给 <code>fetchVideoFromNetwork()</code> 函数……你猜对了——从网络中获取视频。</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>function init() {\n // 逐个遍历视频名称\n for (const video of videos) {\n // 打开事务,获取对象存储,并通过名称获取每个视频\n const objectStore = db.transaction(\"videos_os\").objectStore(\"videos_os\");\n const request = objectStore.get(video.name);\n request.addEventListener(\"success\", () =&gt; {\n // 如果结果存在于数据库中(不是 undefined)\n if (request.result) {\n // 从 IDB 获取视频并使用 displayVideo() 显示它们\n console.log(\"从 IDB 获取视频\");\n displayVideo(\n request.result.mp4,\n request.result.webm,\n request.result.name,\n );\n } else {\n // 从网络获取视频\n fetchVideoFromNetwork(video);\n }\n });\n }\n}\n</code></pre></div>\n </li>\n <li>\n <p>以下片段是从内部 <code>fetchVideoFromNetwork()</code> 获取的——这里我们使用两个单独的 <a href=\"/zh-CN/docs/Web/API/Window/fetch\"><code>fetch()</code></a> 请求获取视频的 MP4 和 WebM 版本。然后,我们使用 <a href=\"/zh-CN/docs/Web/API/Blob\"><code>Body.blob()</code></a> 方法将每个响应的主体提取为 blob,为我们提供可以在以后存储和显示的视频的对象表示。</p>\n <p>我们在这里遇到了一个问题——这两个请求都是异步的,但我们只想在两个 promise 都兑现时尝试显示或存储视频。幸运的是,有一种处理这种问题的内置方法——<a href=\"/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all\"><code>Promise.all()</code></a>。这需要一个参数——引用你要检查放置在数组中的所有 promise 的兑现状态——并返回一个会在所有单独的 promise 都兑现时兑现的 promise。</p>\n <p>在 promise 的 <code>then()</code> 处理器中,我们像之前一样调用 <code>displayVideo()</code> 函数以在 UI 中显示视频,然后我们也调用 <code>storeVideo()</code> 函数将这些视频存储在数据库中。</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 使用 fetch() 函数获取视频的 MP4 和 WebM 版本,\n// 然后将其响应体作为 blob 公开\nconst mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) =&gt;\n response.blob(),\n);\nconst webmBlob = fetch(`videos/${video.name}.webm`).then((response) =&gt;\n response.blob(),\n);\n\n// 只有在两个 Promise 都完成后才运行下一段代码\nPromise.all([mp4Blob, webmBlob]).then((values) =&gt; {\n // 使用 displayVideo() 显示从网络获取的视频\n displayVideo(values[0], values[1], video.name);\n // 使用 storeVideo() 将视频存储到 IDB 中\n storeVideo(values[0], values[1], video.name);\n});\n</code></pre></div>\n </li>\n <li>\n <p>我们首先看一下 <code>storeVideo()</code>。这与你在上一个示例中看到的用于向数据库添加数据的模式非常相似——我们打开一个 <code>readwrite</code> 事务并获取 <code>videos_os</code> 对象存储的引用,创建一个表示要添加到数据库的记录的对象,然后使用 <a href=\"/zh-CN/docs/Web/API/IDBObjectStore/add\"><code>IDBObjectStore.add()</code></a> 添加它。</p>\n <div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 storeVideo() 函数\nfunction storeVideo(mp4, webm, name) {\n // 打开事务,获取对象存储;将其设置为 readwrite,以便我们可以写入 IDB\n const objectStore = db\n .transaction([\"videos_os\"], \"readwrite\")\n .objectStore(\"videos_os\");\n\n // 使用 add() 将记录添加到 IDB 中\n const request = objectStore.add({ mp4, webm, name });\n\n request.addEventListener(\"success\", () =&gt; console.log(\"记录添加尝试完成\"));\n request.addEventListener(\"error\", () =&gt; console.error(request.error));\n}\n</code></pre></div>\n </li>\n <li>\n <p>最后,我们的 <code>displayVideo()</code> 用于创建在 UI 中插入视频然后将它们附加到页面所需的 DOM 元素。最有趣的部分如下所示——要在 <code>&lt;video&gt;</code> 元素中实际显示我们的视频 blob,我们需要使用 <a href=\"/zh-CN/docs/Web/API/URL/createObjectURL_static\" title=\"URL.createObjectURL()\"><code>URL.createObjectURL()</code></a> 方法创建对象 URL(指向存储在内存中的视频 blob 的内部 URL)。完成后,我们可以将对象 URL 设置为 <a href=\"/zh-CN/docs/Web/HTML/Element/source\"><code>&lt;source&gt;</code></a> 元素 <code>src</code> 属性的值,并且它可以正常工作。</p>\n </li>\n</ol>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 定义 displayVideo() 函数\nfunction displayVideo(mp4Blob, webmBlob, title) {\n // 从 blobs 创建对象 URL\n const mp4URL = URL.createObjectURL(mp4Blob);\n const webmURL = URL.createObjectURL(webmBlob);\n\n // 创建 DOM 元素以将视频嵌入到页面中\n const article = document.createElement(\"article\");\n const h2 = document.createElement(\"h2\");\n h2.textContent = title;\n const video = document.createElement(\"video\");\n video.controls = true;\n const source1 = document.createElement(\"source\");\n source1.src = mp4URL;\n source1.type = \"video/mp4\";\n const source2 = document.createElement(\"source\");\n source2.src = webmURL;\n source2.type = \"video/webm\";\n\n // 将 DOM 元素嵌入到页面中\n section.appendChild(article);\n article.appendChild(h2);\n article.appendChild(video);\n video.appendChild(source1);\n video.appendChild(source2);\n}\n</code></pre></div>"}},{"type":"prose","value":{"id":"离线文件存储","title":"离线文件存储","isH3":false,"content":"<p>上面的示例已经说明了如何创建一个将大型资产存储在 IndexedDB 数据库中的应用程序,从而无需多次下载它们。这已经是对用户体验的一个很大的改进,但仍然有一件事——每次访问网站时仍然需要下载主要的 HTML、CSS 和 JavaScript 文件,这意味着当没有网络连接时,它将无法工作。</p>\n<p>\n <img src=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/ff-offline.png\" alt=\"Firefox 离线屏幕,左侧有一幅卡通角色的插图,该角色右手拿着一个两脚插头,左手拿着一个两脚插座。右侧有一个“离线模式”消息和一个标记为“再试一次”的按钮。\" width=\"765\" height=\"307\" loading=\"lazy\">\n</p>\n<p>这就是 <a href=\"/zh-CN/docs/Web/API/Service_Worker_API\">service worker</a> 和密切相关的 <a href=\"/zh-CN/docs/Web/API/Cache\">Cache API</a> 的用武之地。</p>\n<p>service worker 是再被浏览器访问时针对特定来源(网站或某个域的网站的一部分)进行注册的 JavaScript 文件。注册后,它可以控制该来源的可用页面。它通过坐在加载的页面和网络之间以及拦截针对该来源的网络请求来实现这一点。</p>\n<p>当它拦截一个请求时,它可以做任何你想做的事情(参见<a href=\"/zh-CN/docs/Web/API/Service_Worker_API#%E5%85%B6%E4%BB%96%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF\">用例思路</a>),但经典的例子是离线保存网络响应,然后提供响应请求而不是来自网络的响应。实际上,它允许你使网站完全脱机工作。</p>\n<p>Cache API 是另一种客户端存储机制,略有不同——它旨在保存 HTTP 响应,因此与 service worker 一起工作得非常好。</p>"}},{"type":"prose","value":{"id":"service_worker_示例","title":"service worker 示例","isH3":true,"content":"<p>让我们看一个例子,让你对这可能是什么样子有所了解。我们已经创建了另一个版本的视频存储示例,我们在上一节中看到了——这个功能完全相同,只是它还通过 service worker 将 Cache、CSS 和 JavaScript 保存在 Cache API 中,允许示例脱机运行!</p>\n<p>请参阅 <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\" class=\"external\" target=\"_blank\">IndexedDB 视频存储,service worker 在其中运行</a>,并且还可以<a href=\"https://github.com/mdn/learning-area/tree/main/javascript/apis/client-side-storage/cache-sw/video-store-offline\" class=\"external\" target=\"_blank\">查看源代码</a>。</p>\n<h4 id=\"注册_service_worker\">注册 service worker</h4>\n<p>首先要注意的是,在主 JavaScript 文件中放置了一些额外的代码(请参阅 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js\" class=\"external\" target=\"_blank\">index.js</a>)。首先,我们进行特性检测测试,以查看 <code>serviceWorker</code> 的 <a href=\"/zh-CN/docs/Web/API/Navigator\"><code>Navigator</code></a> 对象中是否有该成员。如果返回 true,那么我们知道至少支持 service worker 的基础知识。在这里,我们使用该 <a href=\"/zh-CN/docs/Web/API/ServiceWorkerContainer/register\"><code>ServiceWorkerContainer.register()</code></a> 方法将 <code>sw.js</code> 文件中包含的 service worker 注册到它所驻留的源,因此它可以控制与它或子目录相同的目录中的页面。当其 promise 兑现时,service worker 被视为已注册。</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>// 注册 service worker 以控制使网站离线工作\nif (\"serviceWorker\" in navigator) {\n navigator.serviceWorker\n .register(\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js\",\n )\n .then(() =&gt; console.log(\"已注册 service worker\"));\n}\n</code></pre></div>\n<div class=\"notecard note\">\n <p><strong>备注:</strong> <code>sw.js</code> 文件的给定路径是相对于站点源的,而不是包含代码的 JavaScript 文件。service worker 在 <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>。来源是 <code>https://mdn.github.io</code>,因此给定的路径必须是 <code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code>。如果你想在自己的服务器上托管此示例,则必须相应地更改此示例。这是相当令人困惑的,但出于安全原因,它必须以这种方式工作。</p>\n</div>\n<h4 id=\"安装_service_worker\">安装 service worker</h4>\n<p>下次访问 service worker 控制下的任何页面时(例如,重新加载示例时),将针对该页面安装 service worker,这意味着它将开始控制它。发生这种情况时,<code>install</code> 会向 service worker 发起一个事件;你可以在 service worker 本身内编写代码来响应安装。</p>\n<p>让我们看一下 <a href=\"https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js\" class=\"external\" target=\"_blank\">sw.js</a> 文件(service worker)中的一个例子。你将看到 install 监听器是注册到 <code>self</code> 上的。<code>self</code> 关键字是一种从 service worker 文件内部引用 service worker 的全局作用域的方法。</p>\n<p>在 <code>install</code> 处理器内部,我们使用 <a href=\"/zh-CN/docs/Web/API/ExtendableEvent/waitUntil\"><code>ExtendableEvent.waitUntil()</code></a> 事件对象上可用的方法来表示浏览器不应该完成 service worker 的安装,直到其中的 promise 成功兑现。</p>\n<p>这是我们在运行中看到 Cache API 的地方。我们使用该 <a href=\"/zh-CN/docs/Web/API/CacheStorage/open\"><code>CacheStorage.open()</code></a> 方法打开一个可以存储响应的新缓存对象(类似于 IndexedDB 对象存储)。该 promise 以表示 <code>video-store</code> 缓存的 <a href=\"/zh-CN/docs/Web/API/Cache\"><code>Cache</code></a> 对象来兑现。然后,我们使用该 <a href=\"/zh-CN/docs/Web/API/Cache/addAll\"><code>Cache.addAll()</code></a> 方法获取一系列资产并将其响应添加到缓存中。</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>self.addEventListener(\"install\", (e) =&gt; {\n e.waitUntil(\n caches\n .open(\"video-store\")\n .then((cache) =&gt;\n cache.addAll([\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js\",\n \"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css\",\n ]),\n ),\n );\n});\n</code></pre></div>\n<p>这就是现在,安装完成。</p>\n<h4 id=\"响应未来的请求\">响应未来的请求</h4>\n<p>在我们的 HTML 页面上注册并安装了 service worker,并且所有相关资产都添加到我们的缓存中,我们几乎准备好了。还有一件事要做,写一些代码来响应进一步的网络请求。</p>\n<p>这就是第二位代码的 <code>sw.js</code> 作用。我们向 service worker 全局作用域添加另一个监听器,会在 <code>fetch</code> 引发事件时运行处理函数。只要浏览器在 service worker 注册的目录中请求资产,就会发生这种情况。</p>\n<p>在处理器内部,我们首先记录所请求资产的 URL。然后,我们使用 <a href=\"/zh-CN/docs/Web/API/FetchEvent/respondWith\"><code>FetchEvent.respondWith()</code></a> 方法为请求提供自定义响应。</p>\n<p>在这个块中,我们使用 <a href=\"/zh-CN/docs/Web/API/CacheStorage/match\"><code>CacheStorage.match()</code></a> 来检查是否可以在任何缓存中找到匹配的请求(即匹配 URL)。如果找到匹配,promise 以匹配的响应兑现,如果未找到,则兑现 <code>undefined</code>。</p>\n<p>如果找到匹配项,我们只需将其作为自定义响应返回。如果没有,我们从网络中 <a href=\"/zh-CN/docs/Web/API/Window/fetch\">fetch()</a> 响应并返回该响应。</p>\n<div class=\"code-example\"><div class=\"example-header\"><span class=\"language-name\">js</span></div><pre class=\"brush: js notranslate\"><code>self.addEventListener(\"fetch\", (e) =&gt; {\n console.log(e.request.url);\n e.respondWith(\n caches.match(e.request).then((response) =&gt; response || fetch(e.request)),\n );\n});\n</code></pre></div>\n<p>这就是我们的 service worker。你可以使用它们处理更多的负载——有关详细信息,请参阅 <a href=\"https://github.com/mdn/serviceworker-cookbook/\" class=\"external\" target=\"_blank\">service worker 手册</a>。感谢 Paul Kinlan 的<a href=\"https://developers.google.com/web/fundamentals/codelabs/offline/\" class=\"external\" target=\"_blank\">为你的 Web 应用程序添加 service worker 和离线浏览</a>一文给予这一简单示例的启发。</p>\n<h4 id=\"测试离线示例\">测试离线示例</h4>\n<p>要测试我们的 <a href=\"https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/\" class=\"external\" target=\"_blank\">service worker 示例</a>,你需要加载它几次以确保它已安装。完成后,你可以:</p>\n<ul>\n <li>尝试拔掉网络连接/关闭 Wifi。</li>\n <li>如果你使用的是 Firefox,请选择<em>文件&gt;脱机工作</em>。</li>\n <li>转到开发者工具,然后选择<em>应用 &gt; Service Workers</em>,如果你使用的是 Chrome,请选中<em>离线</em>。</li>\n</ul>\n<p>如果再次刷新示例页面,你仍应该看到它加载得很好。所有内容都是脱机存储的——缓存中的页面资源以及 IndexedDB 数据库中的视频。</p>"}},{"type":"prose","value":{"id":"总结","title":"总结","isH3":false,"content":"<p>现在就是这些。我们希望你能感觉到我们对客户端存储技术的介绍很有用。</p>"}},{"type":"prose","value":{"id":"参见","title":"参见","isH3":false,"content":"<ul>\n <li><a href=\"/zh-CN/docs/Web/API/Web_Storage_API\">Web 存储 API</a></li>\n <li><a href=\"/zh-CN/docs/Web/API/IndexedDB_API\">IndexedDB API</a></li>\n <li><a href=\"/zh-CN/docs/Web/HTTP/Cookies\">Cookie</a></li>\n <li><a href=\"/zh-CN/docs/Web/API/Service_Worker_API\">Service worker API</a></li>\n</ul><ul class=\"prev-next\">\n <li><a class=\"button secondary\" href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs\"><span class=\"button-wrap\"> 上一页 </span></a></li>\n <li><a class=\"button secondary\" href=\"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs\"><span class=\"button-wrap\"> 概述:客户端 Web API</span></a></li>\n \n</ul>"}}],"toc":[{"text":"客户端存储?","id":"客户端存储?"},{"text":"存储简单数据——Web 存储","id":"存储简单数据——web_存储"},{"text":"存储复杂数据——IndexedDB","id":"存储复杂数据——indexeddb"},{"text":"离线文件存储","id":"离线文件存储"},{"text":"总结","id":"总结"},{"text":"参见","id":"参见"}],"summary":"现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。","popularity":0,"modified":"2024-09-22T12:25:49.000Z","other_translations":[{"locale":"de","title":"Client-side storage","native":"Deutsch"},{"locale":"en-US","title":"Client-side storage","native":"English (US)"},{"locale":"es","title":"Almacenamiento del lado cliente","native":"Español"},{"locale":"fr","title":"Stockage côté client","native":"Français"},{"locale":"ja","title":"クライアント側ストレージ","native":"日本語"},{"locale":"pt-BR","title":"Client-side storage","native":"Português (do Brasil)"},{"locale":"ru","title":"Client-side storage","native":"Русский"}],"pageType":"unknown","source":{"folder":"zh-cn/learn/javascript/client-side_web_apis/client-side_storage","github_url":"https://github.com/mdn/translated-content/blob/main/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md","last_commit_url":"https://github.com/mdn/translated-content/commit/3094da9c8210d2598a55afbfde1aa988e280bb05","filename":"index.md"},"short_title":"客户端存储","parents":[{"uri":"/zh-CN/docs/Learn","title":"学习 Web 开发"},{"uri":"/zh-CN/docs/Learn/JavaScript","title":"JavaScript——动态客户端脚本语言"},{"uri":"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs","title":"客户端 Web API"},{"uri":"/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage","title":"客户端存储"}],"pageTitle":"客户端存储 - 学习 Web 开发 | MDN","noIndexing":false}}</script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10