diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/cookies-notice.png b/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/cookies-notice.png deleted file mode 100644 index 8109d4777df92b..00000000000000 Binary files a/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/cookies-notice.png and /dev/null differ diff --git a/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md b/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md index ce17fdaafacff0..626deffb063828 100644 --- a/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md +++ b/files/zh-cn/learn/javascript/client-side_web_apis/client-side_storage/index.md @@ -1,30 +1,31 @@ --- title: 客户端存储 slug: Learn/JavaScript/Client-side_web_APIs/Client-side_storage +l10n: + sourceCommit: bc0d0d1ef796435e969f6d65c7e5d3c08f4023aa --- {{LearnSidebar}} {{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}} -现代 web 浏览器提供了很多在用户电脑 web 客户端存放数据的方法 — 只要用户的允许 — 可以在它需要的时候被重新获得。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留你对其站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。 +现代 Web 浏览器提供了很多在用户电脑上存放数据的方法——只要用户的允许——然后在需要时检索数据。这样能让你存留的数据长时间保存,保存站点和文档在离线情况下使用,保留对站点的个性化配置等等。本篇文章只解释它们工作的一些很基础的部分。 - + - + @@ -32,93 +33,88 @@ slug: Learn/JavaScript/Client-side_web_APIs/Client-side_storage ## 客户端存储? -在其他的 MDN 学习中我们已经讨论过 静态网站([static sites](/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#Static_sites))和动态网站( [dynamic sites](/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#Dynamic_sites))的区别。大多数现代的 web 站点是动态的— 它们在服务端使用各种类型的数据库来存储数据 (服务端存储), 之后通过运行服务端( [server-side](/zh-CN/docs/Learn/Server-side))代码来重新获取需要的数据,把其数据插入到静态页面的模板中,并且生成出 HTML 渲染到用户浏览上。 +在其他的 MDN 学习中我们已经讨论过[静态网站](/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#静态网站)和[动态网站](/zh-CN/docs/Learn/Server-side/First_steps/Client-Server_overview#动态网站)的区别。大多数现代的网站是动态的——它们在服务端使用各种类型的数据库(服务端存储)来存储数据,之后通过运行[服务端](/zh-CN/docs/Learn/Server-side)代码来查询需要的数据,把其数据插入到静态页面的模板中,并将生成的 HTML 提供给客户端,以在用户的浏览器中显示。 -客户端存储以相同的原理工作,但是在使用上有一些不同。它是由 JavaScript APIs 组成的因此允许你在客户端存储数据 (比如在用户的机器上),而且可以在需要的时候重新取得需要的数据。这有很多明显的用处,比如: +客户端存储以相同的原理工作,但是在使用上有一些不同。它是由 JavaScript API 组成的因此允许你在客户端存储数据(比如在用户的机器上),而且可以在需要的时候查询相关的数据。这有很多明显的用处,比如: -- 个性化网站偏好(比如显示一个用户选择的窗口小部件,颜色主题,或者字体)。 -- 保存之前的站点行为 (比如从先前的 session 中获取购物车中的内容,记住用户是否之前已经登陆过)。 -- 本地化保存数据和静态资源可以使一个站点更快(至少让资源变少)的下载,甚至可以在网络失去链接的时候变得暂时可用。 -- 保存 web 已经生产的文档可以在离线状态下访问。 +- 个性化网站偏好(比如显示一个用户选择的自定义微件、颜色主题或字体大小)。 +- 保存之前的站点行为(比如从先前的 session 中获取购物车中的内容,记住用户是否之前已经登陆过)。 +- 本地化保存数据和静态资源可以使一个站点更快(至少让资源变少)的下载,甚至可以在失去网络连接的情况下可用。 +- 将 Web 应用生成的文档保存在本地以供离线使用。 -通常客户端和服务端存储是结合在一起使用的。例如,你可以从数据库中下载一个由网络游戏或音乐播放器应用程序使用的音乐文件,将它们存储在客户端数据库中,并按需要播放它们。用户只需下载音乐文件一次——在随后的访问中,它们将从数据库中检索。 +通常客户端和服务端存储是结合在一起使用的。例如,你可以从数据库中下载一个由 Web 游戏或音乐播放器应用程序使用的音乐文件,将它们存储在客户端数据库中,并按需要播放它们。用户只需下载音乐文件一次——在随后的访问中,它们将从数据库中检索。 > [!NOTE] -> 使用客户端存储 API 可以存储的数据量是有限的(可能是每个 API 单独的和累积的总量);具体的数量限制取决于浏览器,也可能基于用户设置。有关更多信息,获取更多信息,请参考[浏览器存储限制和清理标准](/zh-CN/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)。 +> 使用客户端存储 API 可以存储的数据量是有限的(可能是每个 API 单独的和累积的总量);具体的限制取决于浏览器,也可能基于用户设置。参见[浏览器存储限制和清理标准](/zh-CN/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)以了解更多信息。 -### 传统方法:cookies +### 老做派:cookie -客户端存储的概念已经存在很长一段时间了。从早期的网络时代开始,网站就使用 [cookies](/zh-CN/docs/Web/HTTP/Cookies) 来存储信息,以在网站上提供个性化的用户体验。它们是网络上最早最常用的客户端存储形式。 -因为在那个年代,有许多问题——无论是从技术上的还是用户体验的角度——都是困扰着 cookies 的问题。这些问题非常重要,以至于当第一次访问一个网站时,欧洲居民会收到消息,告诉他们是否会使用 cookies 来存储关于他们的数据,而这是由一项被称为[欧盟 Cookie 条例](/zh-CN/docs/Web/HTTP/Cookies#%E6%AC%A7%E7%9B%9FCookie%E6%8C%87%E4%BB%A4)的欧盟法律导致的。 +客户端存储的概念已经存在很长一段时间了。从 Web 的早期时代开始,网站就使用 [cookie](/zh-CN/docs/Web/HTTP/Cookies) 来存储信息,以在网站上提供个性化的用户体验。它们是 Web 中最早、最常用的客户端存储形式。 -![](cookies-notice.png) +如今,有更简单的机制可用于存储客户端数据,因此我们在本文中不会教授如何使用 cookie。然而,这并不意味着 cookie 在现代 Web 上完全没有用处——它们仍然被广泛用于存储与用户个性化和状态相关的数据,例如会话 ID 和访问令牌。有关 cookie 的更多信息,请参见我们的[使用 HTTP cookie](/zh-CN/docs/Web/HTTP/Cookies) 文章。 -由于这些原因,我们不会在本文中教你如何使用 cookie。毕竟它过时、存在各种[安全问题](/zh-CN/docs/Web/HTTP/Cookies#安全),而且无法存储复杂数据,而且有更好的、更现代的方法可以在用户的计算机上存储种类更广泛的数据。 -cookie 的唯一优势是它们得到了非常旧的浏览器的支持,所以如果你的项目需要支持已经过时的浏览器(比如 Internet Explorer 8 或更早的浏览器),cookie 可能仍然有用,但是对于大多数项目(很明显不包括本站)来说,你不需要再使用它们了。其实 cookie 也没什么好说的,[`document.cookie`](/zh-CN/docs/Web/API/Document/cookie)一把梭就完事了。 +### 新流派:Web 存储和 IndexedDB -> [!NOTE] -> 为什么仍然有新创建的站点使用 cookies?这主要是因为开发人员的习惯,使用了仍然使用 cookies 的旧库,以及存在许多 web 站点,提供了过时的参考和培训材料来学习如何存储数据。 - -### 新流派:Web Storage 和 IndexedDB - -现代浏览器有比使用 cookies 更简单、更有效的存储客户端数据的 API。 +我们在上面所提到的“更简单”的特性如下: -- [Web Storage API](/zh-CN/docs/Web/API/Web_Storage_API) 提供了一种非常简单的语法,用于存储和检索较小的、由名称和相应值组成的数据项。当你只需要存储一些简单的数据时,比如用户的名字,用户是否登录,屏幕背景使用了什么颜色等等,这是非常有用的。 +- [Web 存储 API](/zh-CN/docs/Web/API/Web_Storage_API) 提供了一种非常简单的语法,用于存储和检索较小的、由名称和相应值组成的数据项。当你只需要存储一些简单的数据时,比如用户的名字、用户是否登录、屏幕背景使用了什么颜色等等,这是非常有用的。 - [IndexedDB API](/zh-CN/docs/Web/API/IndexedDB_API) 为浏览器提供了一个完整的数据库系统来存储复杂的数据。这可以用于存储从完整的用户记录到甚至是复杂的数据类型,如音频或视频文件。 你将在下面了解更多关于这些 API 的信息。 -### 未来:Cache API +### Cache API -一些现代浏览器支持新的 {{domxref("Cache")}} API。这个 API 是为存储特定 HTTP 请求的响应文件而设计的,它对于像存储离线网站文件这样的事情非常有用,这样网站就可以在没有网络连接的情况下使用。缓存通常与 [Service Worker API](/zh-CN/docs/Web/API/Service_Worker_API) 组合使用,尽管不一定非要这么做。 -Cache 和 Service Workers 的使用是一个高级主题,我们不会在本文中详细讨论它,尽管我们将在下面的 [离线文件存储](#离线文件存储) 一节中展示一个简单的例子。 +{{domxref("Cache")}} API 是为存储特定 HTTP 请求的响应文件而设计的,它对于像存储离线网站文件这样的事情非常有用,这样网站就可以在没有网络连接的情况下使用。缓存通常与 [Service Worker API](/zh-CN/docs/Web/API/Service_Worker_API) 组合使用,尽管不一定非要这么做。 -## 存储简单数据 — web storage +Cache 和 Service Worker 的使用是一个高级主题,我们不会在本文中详细讨论它,尽管我们将在下面的[离线文件存储](#离线文件存储)小节中展示一个简单的例子。 -[Web Storage API](/zh-CN/docs/Web/API/Web_Storage_API) 非常容易使用 — 你只需存储简单的 键名/键值 对数据 (限制为字符串、数字等类型) 并在需要的时候检索其值。 +## 存储简单数据——Web 存储 + +[Web 存储 API](/zh-CN/docs/Web/API/Web_Storage_API) 非常容易使用——你只需存储简单的键名/键值对数据(限制为字符串、数字等类型)并在需要的时候检索其值。 ### 基本语法 让我们来告诉你怎么做: -1. 第一步,访问 GitHub 上的 [web storage blank template](https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html) (在新标签页打开此[模板](https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html))。 +1. 第一步,访问 GitHub 上的 [Web 存储空白模板](https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html)(在新标签页打开它)。 2. 打开你浏览器开发者工具的 JavaScript 控制台。 -3. 你所有的 web storage 数据都包含在浏览器内两个类似于对象的结构中: {{domxref("Window.sessionStorage", "sessionStorage")}} 和 {{domxref("Window.localStorage", "localStorage")}}。第一种方法,只要浏览器开着,数据就会一直保存 (关闭浏览器时数据会丢失) ,而第二种会一直保存数据,甚至到浏览器关闭又开启后也是这样。我们将在本文中使用第二种方法,因为它通常更有用。 -4. {{domxref("Storage.setItem()")}} 方法允许你在存储中保存一个数据项——它接受两个参数:数据项的名字及其值。试着把它输入到你的 JavaScript 控制台(如果你愿意的话,可以把它的值改为你自己的名字!) +3. 你所有的 Web 存储数据都包含在浏览器内两个类似于对象的结构中:{{domxref("Window.sessionStorage", "sessionStorage")}} 和 {{domxref("Window.localStorage", "localStorage")}}。第一种方法,只要浏览器开着,数据就会一直保存(关闭浏览器时数据会丢失),而第二种会一直保存数据,甚至到浏览器关闭又开启后也是这样。我们将在本文中使用第二种方法,因为它通常更有用。 + + {{domxref("Storage.setItem()")}} 方法允许你在存储中保存一个数据项——它接受两个参数:数据项的名字及其值。试着把它输入到你的 JavaScript 控制台(如果你愿意的话,可以把它的值改为你自己的名字!) ```js localStorage.setItem("name", "Chris"); ``` -5. {{domxref("Storage.getItem()")}} 方法接受一个参数——你想要检索的数据项的名称——并返回数据项的值。现在将这些代码输入到你的 JavaScript 控制台: +4. {{domxref("Storage.getItem()")}} 方法接受一个参数——你想要检索的数据项的名称——并返回数据项的值。现在将这些代码输入到你的 JavaScript 控制台: ```js - var myName = localStorage.getItem("name"); + let myName = localStorage.getItem("name"); myName; ``` - 在输入第二行时,你应该会看到 `myName`变量现在包含`name`数据项的值。 + 在输入第二行时,你应该会看到 `myName` 变量现在包含 `name` 数据项的值。 -6. {{domxref("Storage.removeItem()")}} 方法接受一个参数——你想要删除的数据项的名称——并从 web storage 中删除该数据项。在你的 JavaScript 控制台中输入以下几行: +5. {{domxref("Storage.removeItem()")}} 方法接受一个参数——你想要删除的数据项的名称——并从 Web 存储中删除该数据项。在你的 JavaScript 控制台中输入以下几行: - ```js - localStorage.removeItem("name"); - var myName = localStorage.getItem("name"); - myName; - ``` +```js +localStorage.removeItem("name"); +myName = localStorage.getItem("name"); +myName; +``` - 第三行现在应该返回 `null` — `name` 项已经不存在于 web storage 中。 +第三行现在应该返回 `null`——`name` 项已经不存在于 Web 存储中。 ### 数据会一直存在! -web storage 的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对 localStorage 的而言)。让我们来看看这个: +Web 存储的一个关键特性是,数据在不同页面加载时都存在(甚至是当浏览器关闭后,对 `localStorage` 而言)。让我们来看看这个: -1. 再次打开我们的 Web Storage 空白模板,但是这次你要在不同的浏览器中打开这个教程!这样可以更容易处理。 +1. 再次打开我们的 Web 存储空白模板,但是这次你要在不同的浏览器中打开这个教程!这样可以更容易处理。 2. 在浏览器的 JavaScript 控制台中输入这几行: ```js localStorage.setItem("name", "Chris"); - var myName = localStorage.getItem("name"); + let myName = localStorage.getItem("name"); myName; ``` @@ -128,7 +124,7 @@ web storage 的一个关键特性是,数据在不同页面加载时都存在 4. 再次输入下面几行: ```js - var myName = localStorage.getItem("name"); + let myName = localStorage.getItem("name"); myName; ``` @@ -136,23 +132,23 @@ web storage 的一个关键特性是,数据在不同页面加载时都存在 ### 为每个域名分离储存 -每个域都有一个单独的数据存储区 (每个单独的网址都在浏览器中加载). 你 会看到,如果你加载两个网站(例如 google.com 和 amazon.com)并尝试将某个项目存储在一个网站上,该数据项将无法从另一个网站获取。 +每个域都有一个单独的数据存储区(每个单独的网址都在浏览器中加载)。你会看到,如果你加载两个网站(例如 google.com 和 amazon.com)并尝试将某个项目存储在一个网站上,该数据项将无法从另一个网站获取。 -这是有道理的 - 你可以想象如果网站能够查看彼此的数据,就会出现安全问题! +这是有道理的——你可以想象如果网站能够查看彼此的数据,就会出现安全问题! ### 更复杂的例子 -让我们通过编写一个简单的工作示例来应用这些新发现的知识,让你了解如何使用网络存储。我们的示例将允许你输入一个名称,然后该页面将刷新,以提供个性化问候。这种状态也会页面/浏览器重新加载期间保持,因为这个名称存储在 Web Storage 中。 +让我们通过编写一个简单的工作示例来应用这些新发现的知识,让你了解如何使用网络存储。我们的示例将允许你输入一个名称,然后该页面将刷新,以提供个性化问候。这种状态也会页面/浏览器重新加载期间保持,因为这个名称存储在 Web 存储中。 -你可以在 [personal-greeting.html](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html) 中找到示例文件——这包含一个具有标题,内容和页脚,以及用于输入你的姓名的表单的简单网站。 +你可以在 [personal-greeting.html](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html) 中找到示例 HTML——这包含一个具有标题、内容和页脚,以及用于输入你的姓名的表单的简单网站。 -![](web-storage-demo.png) +![一张网站的截图,包含了页头、内容和页脚部分。页头的左侧有一段欢迎文本,右侧有一个标记为“忘记”的按钮。内容部分包括一个标题,接着是两段占位文本。页脚显示“版权归任何人所有。随意使用代码。”](web-storage-demo.png) 让我们来构建示例,以便了解它的工作原理。 1. 首先,在你的计算机上的新目录中创建一个 [personal-greeting.html](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/web-storage/personal-greeting.html) 文件的副本。 -2. 接下来,请注意我们的 HTML 如何引用一个名为`index.js`的 JavaScript 文件(请参见第 40 行)。我们需要创建它并将 JavaScript 代码写入其中。在与 HTML 文件相同的目录中创建一个`index.js`文件。 -3. 我们首先创建对所有需要在此示例中操作的 HTML 功能的引用 - 我们将它们全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。将以下几行添加到你的 JavaScript 文件中: +2. 接下来,请注意我们的 HTML 如何引用一个名为 `index.js` 的 JavaScript 文件(就像 ``)。我们需要创建它并将 JavaScript 代码写入其中。在与 HTML 文件相同的目录中创建一个 `index.js` 文件。 +3. 我们首先创建对所有需要在此示例中操作的 HTML 特性的引用——我们将它们全部创建为常量,因为这些引用在应用程序的生命周期中不需要更改。将以下几行添加到你的 JavaScript 文件中: ```js // 创建所需的常量 @@ -171,85 +167,80 @@ web storage 的一个关键特性是,数据在不同页面加载时都存在 ```js // 当按钮按下时阻止表单提交 - form.addEventListener("submit", function (e) { - e.preventDefault(); - }); + form.addEventListener("submit", (e) => e.preventDefault()); ``` -5. 现在我们需要添加一个事件监听器,当单击“Say hello”按钮时,它的处理函数将会运行。这些注释详细解释了每一处都做了什么,但实际上我们在这里获取用户输入到文本输入框中的名字并使用`setItem()`将它保存在网络存储中,然后运行一个名为`nameDisplayCheck()`的函数来处理实际的网站文本的更新。将此添加到代码的底部: +5. 现在我们需要添加一个事件监听器,当单击“Say hello”按钮时,它的处理函数将会运行。这些注释详细解释了每一处都做了什么,但实际上我们在这里获取用户输入到文本输入框中的名字并使用 `setItem()` 将它保存在网络存储中,然后运行一个名为 `nameDisplayCheck()` 的函数来处理实际的网站文本的更新。将此添加到代码的底部: ```js - // run function when the 'Say hello' button is clicked - submitBtn.addEventListener("click", function () { - // store the entered name in web storage + // 当点击“Say hello”按钮时运行函数 + submitBtn.addEventListener("click", () => { + // 将输入的名字存储到网页存储中 localStorage.setItem("name", nameInput.value); - // run nameDisplayCheck() to sort out displaying the - // personalized greetings and updating the form display + // 运行 nameDisplayCheck() 来处理显示个性化问候语和更新表单显示 nameDisplayCheck(); }); ``` -6. 此时,我们还需要一个事件处理程序,以便在单击“Forget”按钮时运行一个函数——且仅在单击“Say hello”按钮(两种表单状态来回切换)后才显示。在这个功能中,我们使用`removeItem()`从网络存储中删除项目`name`,然后再次运行`nameDisplayCheck()`以更新显示。将其添加到底部: +6. 此时,我们还需要一个事件处理器,以便在单击“Forget”按钮时运行一个函数——且仅在单击“Say hello”按钮(两种表单状态来回切换)后才显示。在这个函数中,我们使用 `removeItem()` 从 Web 存储中删除项目 `name`,然后再次运行 `nameDisplayCheck()` 以更新显示。将其添加到底部: ```js - // run function when the 'Forget' button is clicked - forgetBtn.addEventListener("click", function () { - // Remove the stored name from web storage + // 当点击“Forget”按钮时运行函数 + forgetBtn.addEventListener("click", () => { + // 从网页存储中移除存储的名字 localStorage.removeItem("name"); - // run nameDisplayCheck() to sort out displaying the - // generic greeting again and updating the form display + // 运行 nameDisplayCheck() 来重新显示通用问候语并更新表单显示 nameDisplayCheck(); }); ``` -7. 现在是时候定义`nameDisplayCheck()`函数本身了。在这里,我们通过使用`localStorage.getItem('name')`作为测试条件来检查 name 数据项是否已经存储在 Web Storage 中。如果它已被存储,则该调用的返回值为`true`; 如果没有,它会是`false`。如果是`true`,我们会显示个性化问候语,显示表格的“forget”部分,并隐藏表格的“Say hello”部分。如果是`false`,我们会显示一个通用问候语,并做相反的事。再次将下面的代码添到底部: +7. 现在是时候定义 `nameDisplayCheck()` 函数本身了。在这里,我们通过使用 `localStorage.getItem('name')` 作为测试条件来检查 name 数据项是否已经存储在 Web 存储中。如果它已被存储,则该调用的返回值为 `true`;果没有,它会是 `false`。如果是 `true`,我们会显示个性化问候语,显示表格的“forget”部分,并隐藏表格的“Say hello”部分。如果是 `false`,我们会显示一个通用问候语,并做相反的事。再次将下面的代码添到底部: ```js - // define the nameDisplayCheck() function + // 定义 nameDisplayCheck() 函数 function nameDisplayCheck() { - // check whether the 'name' data item is stored in web Storage + // 检查 'name' 数据项是否存储在网页存储中 if (localStorage.getItem("name")) { - // If it is, display personalized greeting - let name = localStorage.getItem("name"); - h1.textContent = "Welcome, " + name; - personalGreeting.textContent = - "Welcome to our website, " + - name + - "! We hope you have fun while you are here."; - // hide the 'remember' part of the form and show the 'forget' part + // 如果存在,显示个性化问候语 + const name = localStorage.getItem("name"); + h1.textContent = `欢迎,${name}`; + personalGreeting.textContent = `欢迎来到我们的网站,${name}!希望您在这里玩得开心。`; + // 隐藏表单中的 'remember' 部分,显示 'forget' 部分 forgetDiv.style.display = "block"; rememberDiv.style.display = "none"; } else { - // if not, display generic greeting - h1.textContent = "Welcome to our website "; + // 如果不存在,显示通用问候语 + h1.textContent = "欢迎来到我们的网站"; personalGreeting.textContent = - "Welcome to our website. We hope you have fun while you are here."; - // hide the 'forget' part of the form and show the 'remember' part + "欢迎来到我们的网站。希望您在这里玩得开心。"; + // 隐藏表单中的 'forget' 部分,显示 'remember' 部分 forgetDiv.style.display = "none"; rememberDiv.style.display = "block"; } } ``` -8. 最后但同样重要的是,我们需要在每次加载页面时运行`nameDisplayCheck()`函数。如果我们不这样做,那么个性化问候不会在页面重新加载后保持。将以下代码添加到代码的底部: +8. 最后但同样重要的是,我们需要在每次加载页面时运行 `nameDisplayCheck()` 函数。如果我们不这样做,那么个性化问候不会在页面重新加载后保持。将以下代码添加到代码的底部: ```js - document.body.onload = nameDisplayCheck; + nameDisplayCheck(); ``` -你的例子完成了 - 做得好!现在剩下的就是保存你的代码并在浏览器中测试你的 HTML 页面。你可以在这里看到我们的[完成版本并在线运行](https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html)。 +你的例子完成了——做得好!现在剩下的就是保存你的代码并在浏览器中测试你的 HTML 页面。你可以在这里看到我们的[完成版本并在线运行](https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html)。 > [!NOTE] -> 在 [Using the Web Storage API](/zh-CN/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) 中还有一个稍微复杂点儿的示例。 +> 在[使用 Web 存储 API](/zh-CN/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) 中还有一个稍微复杂点儿的示例。 > [!NOTE] -> 在完成版本的源代码中, `` 一行里, `defer` 属性指明在页面加载完成之前,{{htmlelement("script")}}元素的内容不会执行。 +> 在完成版本的源代码中,`` 一行里,`defer` 属性指明在页面加载完成之前,{{htmlelement("script")}} 元素的内容不会执行。 -## 存储复杂数据 — IndexedDB +## 存储复杂数据——IndexedDB [IndexedDB API](/zh-CN/docs/Web/API/IndexedDB_API)(有时简称 IDB)是可以在浏览器中访问的一个完整的数据库系统,在这里,你可以存储复杂的关系数据。其种类不限于像字符串和数字这样的简单值。你可以在一个 IndexedDB 中存储视频,图像和许多其他的内容。 -但是,这确实是有代价的:使用 IndexedDB 要比 Web Storage API 复杂得多。在本节中,我们仅仅只能浅尝辄止地一提它的能力,不过我们会给你足够基础知识以帮助你开始。 +IndexedDB API 允许你创建一个数据库,然后在该数据库中创建对象存储。对象存储类似于关系型数据库中的表,每个对象存储可以包含多个对象。要了解有关 IndexedDB API 的更多信息,请参见[使用 IndexedDB](/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB)。 + +但是,这确实是有代价的:使用 IndexedDB 要比 Web 存储 API 复杂得多。在本节中,我们仅仅只能浅尝辄止地一提它的能力,不过我们会给你足够基础知识以帮助你开始。 ### 通过一个笔记存储示例演示 @@ -257,17 +248,17 @@ web storage 的一个关键特性是,数据在不同页面加载时都存在 这个应用看起来像这样: -![](idb-demo.png) +![IndexDB 笔记演示的截图包含四个部分。第一部分是页头。第二部分列出了所有已创建的笔记,包括两条笔记,每条笔记都有一个删除按钮。第三部分是一个表单,包含两个输入字段用于“笔记标题”和“笔记内容”,以及一个标记为“创建新笔记”的按钮。底部部分的页脚显示“版权归任何人所有。随意使用代码。”](idb-demo.png) 每个笔记都有一个标题和一些正文,每个都可以单独编辑。我们将在下面通过的 JavaScript 代码提供详细的笔记,以帮助你了解正在发生的事情。 ### 开始 -1、首先,将 [`index.html`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html), [`style.css`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css), 和 [`index-start.js`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js) 文件的本地副本放入本地计算机上的新目录中。 +1、首先,将 [`index.html`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index.html)、[`style.css`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/style.css) 和 [`index-start.js`](https://github.com/mdn/learning-area/blob/main/javascript/apis/client-side-storage/indexeddb/notes/index-start.js) 文件的本地副本放入本地计算机上的新目录中。 -2、浏览这些文件。你将看到 HTML 非常简单:具有页眉和页脚的网站,以及包含显示笔记的位置的主内容区域,以及用于在数据库中输入新笔记的表单。CSS 提供了一些简单的样式,使其更清晰。JavaScript 文件包含五个声明的常量,其中包含对将显示笔记的 {{htmlelement("ul")}} 元素的引用,标题和正文 {{htmlelement("input")}} 元素,{{htmlelement("form")}} 本身,以及{{htmlelement("button")}}。 +2、浏览这些文件。你将看到 HTML 非常简单:具有页眉和页脚的网站,以及包含显示笔记的位置的主内容区域,以及用于在数据库中输入新笔记的表单。CSS 提供了一些简单的样式,使其更清晰。JavaScript 文件包含五个声明的常量,其中包含对将显示笔记的 {{htmlelement("ul")}} 元素的引用、标题和正文 {{htmlelement("input")}} 元素、{{htmlelement("form")}} 本身,以及 {{htmlelement("button")}}。 -3、将你的 JavaScript 文件重命名为 `index.js` 。你现在可以开始向其添加代码了。 +3、将你的 JavaScript 文件重命名为 `index.js`。你现在可以开始向其添加代码了。 ### 数据库初始设置 @@ -276,145 +267,136 @@ web storage 的一个关键特性是,数据在不同页面加载时都存在 1. 在常量声明下,加入这几行: ```js - // Create an instance of a db object for us to store the open database in + // 创建一个 db 对象的实例,用于存储打开的数据库 let db; ``` - 这里我们声明了一个叫 `db` 的变量 — 这将在之后被用来存储一个代表数据库的对象。我们将在几个地方使用它,所以我们为了方便使用而在这里把它声明为全局的。 + 这里我们声明了一个叫 `db` 的变量——这将在之后被用来存储一个代表数据库的对象。我们将在几个地方使用它,所以我们为了方便使用而在这里把它声明为全局的。 -2. 接着,在你的代码最后添加如下代码: +2. 接着,添加如下代码: ```js - window.onload = function () {}; + // 打开我们的数据库;如果数据库不存在,将会创建它 + // (请参见下面的 upgradeneeded 处理器) + const openRequest = window.indexedDB.open("notes_db", 1); ``` - 我们将把所有的后续代码写在这个 `window.onload` 事件处理函数内,这个函数将在 window 的 [`load`](/zh-CN/docs/Web/API/Window/load_event) 事件被触发时调用,为了确保我们没有在应用完整加载前试图使用 IndexedDB 功能(如果我们不这么做,它会失败)。 - -3. 在`window.onload`处理程序内,添加以下内容: - - ```js - // Open our database; it is created if it doesn't already exist - // (see onupgradeneeded below) - let request = window.indexedDB.open("notes", 1); - ``` + 这一行代码创建了一个请求,用于打开名为 `notes_db` 的版本 `1` 的数据库。如果该数据库尚不存在,它将由后续代码创建。你会在 IndexedDB 中经常看到这种请求模式。数据库操作需要时间。你不希望在等待结果时使浏览器卡死,因此数据库操作是{{Glossary("asynchronous", "异步")}}的,意味着操作不会立即发生,而是在未来的某个时间发生,并且你会在操作完成时收到通知。 - 此行创建一个 `request` 变量,目的是打开 `notes`数据库的 `1`版本。如果`notes`数据库不存在,则后续代码将为你创建。你将在 IndexedDB 中经常看到此请求模式。数据库操作需要时间。你不希望在等待结果时挂起浏览器,因此数据库操作是[异步的](/zh-CN/docs/Glossary/asynchronous),这意味着它们不会立即发生,而是在将来的某个时刻发生,并且在完成后会收到通知。 - - 要在 IndexedDB 中处理此问题,你需要创建一个请求对象(可以随意命名 - 命名为`request`,可以表明它的用途)。然后,在请求完成或者失败时,使用事件处理程序来运行代码,你将在下面看到这些代码。 + 在 IndexedDB 中处理这一点的方法是创建一个请求对象(可以随意命名——我们在这里称之为 `openRequest`,这样它的用途就很明显)。然后,你可以使用事件处理器来运行代码,当请求完成、失败等时,你可以看到下面的用法。 > [!NOTE] - > 版本号很重要。如果要升级数据库(例如:更改表结构),则必须使用增加的版本号或者`onupgradeneeded`处理程序内指定的不同模式(请参阅下文)等再次运行代码。在这个简单教程中,我们不讨论数据库升级。 + > 版本号很重要。如果你想升级数据库(例如,通过更改表结构),你需要再次运行代码,增加版本号,并在 `upgradeneeded` 处理器中指定不同的模式等。我们在本教程中不会涉及数据库的升级。 - 1. 在之前添加的事件处理程序下方添加以下代码 - 在`window.onload`处理程序内: +3. 现在,在你之前添加的代码下面添加以下事件处理器: - ```js - // onerror handler signifies that the database didn't open successfully - request.onerror = function () { - console.log("Database failed to open"); - }; + ```js + // 错误处理器表示数据库未成功打开 + openRequest.addEventListener("error", () => console.error("数据库打开失败")); - // onsuccess handler signifies that the database opened successfully - request.onsuccess = function () { - console.log("Database opened successfully"); + // 成功处理器表示数据库成功打开 + openRequest.addEventListener("success", () => { + console.log("数据库成功打开"); - // Store the opened database object in the db variable. This is used a lot below - db = request.result; + // 将打开的数据库对象存储在 db 变量中。下面会多次使用 + db = openRequest.result; - // Run the displayData() function to display the notes already in the IDB - displayData(); - }; - ``` + // 运行 displayData() 函数以显示已存在于 IDB 中的笔记 + displayData(); + }); + ``` - 如果系统返回:请求失败,[`request.onerror`](/zh-CN/docs/Web/API/IDBRequest/onerror)将会运行。这将允许你对这个问题做出响应。在我们的简单示例中,只是将消息打印到 JavaScript 控制台。 + {{domxref("IDBRequest/error_event", "error")}} 事件处理器会在系统返回请求失败的消息时运行。这允许你对这个问题做出响应。在我们的示例中,我们只是将一条消息打印到 JavaScript 控制台。 - 如果系统返回:请求成功,表明成功打开数据库,[`request.onsuccess`](/zh-CN/docs/Web/API/IDBRequest/onsuccess)将会运行。如果是这种情况,则表示已打开数据库的对象在[`request.result`](/zh-CN/docs/Web/API/IDBRequest/result)属性中变为可用,从而允许我们操作数据库。我们将它存储在`db`我们之前创建的变量中供以后使用。我们还运行了一个名为 `displayData()` 的函数,它在 {{HTMLElement("ul")}} 内显示数据库中的数据。我们现在运行它,以便在页面加载时立即显示已经在数据库中的笔记。你将在后面看到 `displayData()` 的定义。 + {{domxref("IDBRequest/success_event", "success")}} 事件处理器会在请求成功返回时运行,意味着数据库已成功打开。如果是这种情况,表示打开的数据库的对象会在 {{domxref("IDBRequest.result", "openRequest.result")}} 属性中提供,允许我们操作数据库。我们将其存储在之前创建的 `db` 变量中以供后续使用。我们还会运行一个名为 `displayData()` 的函数,用于在 HTML 中的 `ul` 元素内显示数据库中的数据。我们现在运行它,以便在页面加载时立即显示数据库中已经存在的笔记。你会在稍后看到 `displayData()` 的定义。 -4. 最后,对于本节,我们可能会添加最重要的事件处理程序来设置数据库:[`request.onupgradeneeded`](/zh-CN/docs/Web/API/IDBOpenDBRequest/onupgradeneeded)。如果尚未设置数据库,或者使用比现有存储数据库更大的版本号打开数据库(执行升级时),则运行此处理程序。在上一个处理程序下面添加以下代码: +4. 最后,为了完成这一部分,我们将添加可能是设置数据库时最重要的事件处理器:{{domxref("IDBOpenDBRequest/upgradeneeded_event", "upgradeneeded")}}。如果数据库尚未设置,或数据库以比现有存储的数据库更大的版本号打开(进行升级时),该处理器会运行。在你之前的处理器下面添加以下代码: ```js - // Setup the database tables if this has not already been done - request.onupgradeneeded = function (e) { - // Grab a reference to the opened database - let db = e.target.result; - - // Create an objectStore to store our notes in (basically like a single table) - // including a auto-incrementing key - let objectStore = db.createObjectStore("notes", { + // 如果尚未设置数据库表,则进行设置 + openRequest.addEventListener("upgradeneeded", (e) => { + // 获取已打开的数据库的引用 + db = e.target.result; + + // 在我们的数据库中创建一个用于存储笔记和自增键的 objectStore + // objectStore 类似于关系数据库中的“表” + const objectStore = db.createObjectStore("notes_os", { keyPath: "id", autoIncrement: true, }); - // Define what data items the objectStore will contain + // 定义 objectStore 将包含的数据项 objectStore.createIndex("title", "title", { unique: false }); objectStore.createIndex("body", "body", { unique: false }); - console.log("Database setup complete"); - }; + console.log("数据库设置完成"); + }); ``` - 这是我们定义数据库的模式(结构)的地方; 也就是说,它包含的列(或字段)集。这里我们首先从`e.target.result`(事件目标的`result`属性)中获取对现有数据库的引用,该引用是`request`对象。这相当于处理程序`db = request.result;`内部的行`onsuccess`,但我们需要在此单独执行此操作,因为`onupgradeneeded`处理程序(如果需要)将在`onsuccess`处理程序之前运行,这意味着`db`如果我们不这样做,该值将不可用。 + 在这里我们定义了数据库的模式(结构);即它包含的列(或字段)集合。首先,我们从事件的目标 (`e.target.result`) 的 `result` 属性中获取现有数据库的引用,这就是 `request` 对象。这等同于在`成功`事件处理器中的 `db = openRequest.result;`,但我们需要在这里单独进行,因为 `upgradeneeded` 事件处理器(如果需要的话)会在`成功`事件处理器之前运行,这意味着如果我们不这样做,`db` 值将不可用。 - 然后,我们使用 [`IDBDatabase.createObjectStore()`](/zh-CN/docs/Web/API/IDBDatabase/createObjectStore) 在打开的数据库中创建一个新的对象库。这相当于传统数据库系统中的单个表。我们将其命名为 notes,并且还指定了一个名为 `id` 的 `autoIncrement` 关键字段——在每个新记录中,这将自动赋予增量值——开发人员不需要明确地设置它。作为密钥,`id` 字段将用于唯一标识记录,例如删除或显示记录时。 + 然后,我们使用 {{domxref("IDBDatabase.createObjectStore()")}} 在打开的数据库中创建一个名为 `notes_os` 的新 objectStore。这相当于传统数据库系统中的一个表。我们给它指定了名称 `notes`,并指定了一个 `autoIncrement` 键字段 `id`——在每条新记录中,这个字段会自动分配递增的值——开发者不需要显式设置它。作为键,`id` 字段将用于唯一标识记录,例如在删除或显示记录时。 - 我们还使用以下[`IDBObjectStore.createIndex()`](/zh-CN/docs/Web/API/IDBObjectStore/createIndex)方法创建另外两个索引(字段):( `title`每个音符将包含一个标题),以及`body`(包含音符的正文)。 + 我们还使用 {{domxref("IDBObjectStore.createIndex()")}} 方法创建了两个其他索引(字段):`title`(包含每条笔记的标题)和 `body`(包含笔记的正文内容)。 -因此,通过设置这个简单的数据库模式,当我们开始向数据库添加记录时,每个记录都会沿着这些行表示为一个对象: + 设置好这个数据库模式后,当我们开始向数据库中添加记录时,每条记录将表示为类似于以下格式的对象: -```js -{ - title: "Buy milk", - body: "Need both cows milk and soya.", - id: 8 -} -``` + ```json + { + "title": "Buy milk", + "body": "Need both cows milk and soy.", + "id": 8 + } + ``` ### 添加数据到数据库 现在让我们看一下如何将记录添加到数据库中。这将使用我们页面上的表单完成。 -在你之前的事件处理程序下面,添加以下一行,它设置了一个 `submit` 事件处理程序,当表单被提交时(当提交 {{htmlelement("button")}} 元素被按下导致表单成功提交),运行一个叫做 `addData()` 的函数: +在你之前的事件处理器下面,添加以下一行,它设置了一个 `submit` 事件处理器,当表单被提交时(当提交 {{htmlelement("button")}} 元素被按下导致表单成功提交),运行一个叫做 `addData()` 的函数: ```js -// Create an onsubmit handler so that when the form is submitted the addData() function is run -form.onsubmit = addData; +// 创建一个提交事件处理器,当表单提交时运行 addData() 函数 +form.addEventListener("submit", addData); ``` -现在让我们定义一下这个`addData()`功能。在上一行下面添加: +现在让我们定义 `addData()` 函数。在你之前的代码下面添加以下内容: ```js -// Define the addData() function +// 定义 addData() 函数 function addData(e) { - // prevent default - we don't want the form to submit in the conventional way + // 阻止默认行为——我们不希望表单以传统方式提交 e.preventDefault(); - // grab the values entered into the form fields and store them in an object ready for being inserted into the DB - let newItem = { title: titleInput.value, body: bodyInput.value }; + // 获取输入字段中输入的值,并将它们存储在一个对象中,准备插入到数据库中 + const newItem = { title: titleInput.value, body: bodyInput.value }; + + // 打开一个读/写事务,准备添加数据 + const transaction = db.transaction(["notes_os"], "readwrite"); - // open a read/write db transaction, ready for adding the data - let transaction = db.transaction(["notes"], "readwrite"); + // 调用已添加到数据库中的 objectStore + const objectStore = transaction.objectStore("notes_os"); - // call an object store that's already been added to the database - let objectStore = transaction.objectStore("notes"); + // 发起请求,将我们的 newItem 对象添加到 objectStore 中 + const addRequest = objectStore.add(newItem); - // Make a request to add our newItem object to the object store - var request = objectStore.add(newItem); - request.onsuccess = function () { - // Clear the form, ready for adding the next entry + addRequest.addEventListener("success", () => { + // 清空表单,为添加下一个条目做好准备 titleInput.value = ""; bodyInput.value = ""; - }; + }); - // Report on the success of the transaction completing, when everything is done - transaction.oncomplete = function () { - console.log("Transaction completed: database modification finished."); + // 在事务完成时报告成功,当所有操作完成后 + transaction.addEventListener("complete", () => { + console.log("事务完成:数据库修改结束。"); - // update the display of data to show the newly added item, by running displayData() again. + // 通过再次运行 displayData() 来更新数据的显示,以显示新添加的条目 displayData(); - }; + }); - transaction.onerror = function () { - console.log("Transaction not opened due to error"); - }; + transaction.addEventListener("error", () => + console.log("事务未成功打开,出现错误"), + ); } ``` @@ -425,119 +407,116 @@ function addData(e) { - 使用 {{domxref("IDBDatabase.transaction()")}} 方法打开 `notes` 对象存储的 `readwrite` 事务。此事务对象允许我们访问对象存储,以便我们可以对其执行某些操作,例如添加新记录。 - 使用 {{domxref("IDBTransaction.objectStore()")}} 方法访问对象库,将结果保存在 `objectStore` 变量中。 - 使用 {{domxref("IDBObjectStore.add()")}} 添加新记录到数据库。这创建了一个请求对象,与我们之前看到的方式相同。 -- 在生命周期的关键点为 `request` 以及 `transaction` 对象添加事件处理程序以运行代码。请求成功后,我们会清除表单输入,以便输入下一个笔记。交易完成后,我们再次运行 `displayData()` 函数以更新页面上的笔记显示。 +- 在生命周期的关键点为 `request` 以及 `transaction` 对象添加事件处理器以运行代码。请求成功后,我们会清除表单输入,以便输入下一个笔记。交易完成后,我们再次运行 `displayData()` 函数以更新页面上的笔记显示。 ### 显示数据 我们已经在代码中引用了 `displayData()` 两次,所以我们可能更好地定义它。将其添加到你的代码中,位于上一个函数定义之下: ```js -// Define the displayData() function +// 定义 displayData() 函数 function displayData() { - // Here we empty the contents of the list element each time the display is updated - // If you ddn't do this, you'd get duplicates listed each time a new note is added + // 每次更新显示时,我们都清空列表元素的内容 + // 如果不这样做,每次添加新笔记时列表中会出现重复项 while (list.firstChild) { list.removeChild(list.firstChild); } - // Open our object store and then get a cursor - which iterates through all the - // different data items in the store - let objectStore = db.transaction("notes").objectStore("notes"); - objectStore.openCursor().onsuccess = function (e) { - // Get a reference to the cursor - let cursor = e.target.result; + // 打开我们的对象存储,然后获取游标——它会迭代存储中的所有数据项 + const objectStore = db.transaction("notes_os").objectStore("notes_os"); + objectStore.openCursor().addEventListener("success", (e) => { + // 获取游标的引用 + const cursor = e.target.result; - // If there is still another data item to iterate through, keep running this code + // 如果还有数据项需要迭代,则继续运行此代码 if (cursor) { - // Create a list item, h3, and p to put each data item inside when displaying it - // structure the HTML fragment, and append it inside the list - let listItem = document.createElement("li"); - let h3 = document.createElement("h3"); - let para = document.createElement("p"); + // 创建一个列表项、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); - // Put the data from the cursor inside the h3 and para + // 将游标中的数据放入 h3 和 para 中 h3.textContent = cursor.value.title; para.textContent = cursor.value.body; - // Store the ID of the data item inside an attribute on the listItem, so we know - // which item it corresponds to. This will be useful later when we want to delete items + // 将数据项的 ID 存储在 listItem 的一个属性中,以便我们知道 + // 这项数据对应哪个条目。这在稍后删除条目时会很有用 listItem.setAttribute("data-note-id", cursor.value.id); - // Create a button and place it inside each listItem - let deleteBtn = document.createElement("button"); + // 创建一个按钮,并将其放置在每个 listItem 中 + const deleteBtn = document.createElement("button"); listItem.appendChild(deleteBtn); - deleteBtn.textContent = "Delete"; + deleteBtn.textContent = "删除"; - // Set an event handler so that when the button is clicked, the deleteItem() - // function is run - deleteBtn.onclick = deleteItem; + // 设置事件处理器,当按钮被点击时,运行 deleteItem() 函数 + deleteBtn.addEventListener("click", deleteItem); - // Iterate to the next item in the cursor + // 迭代到游标中的下一个项 cursor.continue(); } else { - // Again, if list item is empty, display a 'No notes stored' message + // 如果列表为空,则显示“没有存储的笔记”消息 if (!list.firstChild) { - let listItem = document.createElement("li"); - listItem.textContent = "No notes stored."; + const listItem = document.createElement("li"); + listItem.textContent = "没有存储的笔记。"; list.appendChild(listItem); } - // if there are no more cursor items to iterate through, say so - console.log("Notes all displayed"); + // 如果没有更多的游标项需要迭代,说明所有笔记都已显示 + console.log("所有笔记已显示"); } - }; + }); } ``` 再次,让我们打破这个: - 首先,我们清空 [`
Prerequisites:前提: - JavaScript 基础 (查看 - 第一步, - 构建的块, - JavaScript 对象), - 第一步构建代码块JavaScript 对象)、基础的客户端 API
Objective:目标: 学习如何使用客户端存储 API 来存储应用数据。