引言
在现代Web开发中,localStorage是HTML5引入的一个非常实用的功能,它允许我们在用户的浏览器中存储数据,并且这些数据即使在页面刷新或关闭浏览器后仍然存在。这对于构建需要保存用户偏好设置、缓存数据或实现简单的离线功能的Web应用来说非常有用。今天,我们将通过一个具体的例子——创建一个可以添加和持久化西班牙小吃(Tapas)项目的列表来深入了解localStorage的使用。
什么是 localStorage?
localStorage 是一种Web存储机制,它为每个原点(即协议+域名+端口)提供了一定大小的空间(通常大约5MB),用来存储键值对形式的数据。与sessionStorage不同的是,localStorage中的数据没有过期时间,除非被手动清除或覆盖,否则将一直保留在用户的设备上。
以下是localStorage在浏览器中的位置以及页面展示刷新也不会改变localStorage中的值
使用 localStorage 构建 Tapas 列表
我们先来看看这个项目的基本结构:
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<ul class="plates">
<li>Loading Taps...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
</div>
这段代码定义了一个简单的界面,包括一个标题、一个用于显示Tapas项目的无序列表以及一个用于添加新项目的表单。
接下来,让我们看看如何利用localStorage来保存和加载这些Tapas项目:
const addItems = document.querySelector('.add-items'); // 获取表单元素
const itemsList = document.querySelector('.plates'); // 获取列表元素
let items = JSON.parse(localStorage.getItem('items')) || []; // 尝试从 localStorage 中获取已有的 Tapas 列表
// 添加新 Tapas 项目
function addItem(event) {
event.preventDefault(); // 阻止表单默认提交行为
const text = (this.querySelector('[name=item]')).value.trim(); // 获取用户输入的文本
if (!text) return; // 如果输入为空,则不执行任何操作
const item = { text, done: false }; // 创建一个新的 Tapas 项目对象
items.push(item); // 将新项目添加到列表中
populateList(items, itemsList); // 更新页面上的列表视图
localStorage.setItem('items', JSON.stringify(items)); // 将更新后的列表保存回 localStorage
this.reset(); // 重置表单以便于下一次输入
}
// 渲染 Tapas 列表
function populateList(plates, platesList) {
platesList.innerHTML = plates.map((plate, i) => `
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''}>
<label for="item${i}">${plate.text}</label>
</li>
`).join(''); // 使用 map 方法遍历数组并生成列表项,最后用 join 方法连接成字符串设置为列表的内容
}
addItems.addEventListener('submit', addItem); // 给表单添加提交事件监听器
populateList(items, itemsList); // 页面加载完成后初始化列表视图
在这段JavaScript代码中,我们做了以下几件事:
- 读取已有数据:尝试从
localStorage中读取名为items的数据。如果不存在这样的数据,则初始化为空数组。 - 处理表单提交:当用户提交表单时,阻止默认行为,收集用户输入的信息,创建一个新的Tapas项目,并将其添加到
items数组中。 - 更新UI:调用
populateList函数,根据当前的items数组动态地渲染列表。 - 保存数据:每次添加新的Tapas项目后,都将最新的
items数组转换为JSON格式并保存到localStorage中,确保数据的持久性。
下面是代码的详细解释:
1.初始阶段
const addItems = document.querySelector('.add-items'); // form
const itemsList = document.querySelector('.plates'); // ul
const items = JSON.parse(localStorage.getItem('items')) || [];
如果你是一个新手小白看到document应该会很懵逼,下面让我简要介绍一下document
document 是浏览器环境中全局对象 window 的一个属性,它表示当前网页的文档(Document)对象。document 对象是Web页面与JavaScript之间的接口,允许开发者访问和操作HTML或XML文档的内容、结构和样式。它是DOM(文档对象模型,Document Object Model)的核心部分,提供了对页面中所有元素的访问和控制能力。
document的主要功能
- 访问和修改内容:通过
document可以获取页面中的文本内容、属性值等,并且能够动态地添加、删除或更改HTML内容。 - 查找元素:提供了一系列方法用于选择页面上的元素,如
getElementById()、getElementsByClassName()、getElementsByTagName()、querySelector()和querySelectorAll()等。 - 创建和操作节点:可以创建新的HTML元素(如
createElement())、文本节点(如createTextNode()),还可以插入、移除或替换这些节点。 - 事件处理:为页面元素添加事件监听器,使得可以在特定事件发生时执行JavaScript代码,比如点击按钮、提交表单等。
- 样式操作:直接修改元素的CSS样式,或者通过类名来应用预定义的样式规则。
既然已经知道document的用法让我们逐行详细解释每一部分的作用。
1. const addItems = document.querySelector('.add-items'); // form
这行代码使用了document.querySelector()方法来选择页面中具有类名.add-items的第一个元素,并将其赋值给常量addItems。根据HTML结构,.add-items是一个表单元素(<form>),用于添加新的Tapas项目。querySelector()返回的是一个DOM元素对象,因此addItems现在引用的就是这个表单。通过这种方式获取到的DOM元素可以用来监听用户事件(如提交表单)或操作该元素的属性和样式。
2. const itemsList = document.querySelector('.plates'); // ul
同样的,这里也是使用document.querySelector()来选择具有类名.plates的第一个元素,并将它赋值给常量itemsList。在这个例子中,.plates对应的是一个无序列表(<ul>),它用于展示所有的Tapas项目。itemsList变量现在引用的就是这个列表元素,之后可以通过它来动态地添加、移除或更新列表项。
3. const items = JSON.parse(localStorage.getItem('items')) || [];
这一行稍微复杂一些,它涉及到了浏览器提供的localStorage API以及JavaScript的对象解析:
-
localStorage.getItem('items'):- 这个方法尝试从浏览器的
localStorage中读取名为'items'的数据项。localStorage是一种持久化的存储机制,允许网页在用户的浏览器中保存数据,即使浏览器关闭后这些数据依然存在。
- 这个方法尝试从浏览器的
-
JSON.parse(...):localStorage只能存储字符串形式的数据,因此当我们想要存储复杂的数据结构(如数组或对象)时,通常需要先将它们转换成JSON格式(即字符串)。这里我们使用JSON.parse()方法来解析从localStorage中取出的字符串,将其转换回原始的JavaScript对象或数组。
-
|| []:- 这是一个简写的逻辑运算符表达式。如果
JSON.parse(localStorage.getItem('items'))的结果是null或undefined(即没有找到对应的存储项),那么整个表达式的值就会是右边的空数组[]。这样做是为了确保items始终是一个数组,即使之前从未向localStorage中添加过任何数据。
- 这是一个简写的逻辑运算符表达式。如果
总结来说,这三行代码完成了以下任务:
- 获取页面上的表单元素并将其存储在
addItems变量中,以便稍后处理用户输入。 - 获取用于显示Tapas项目的无序列表元素,并将其存储在
itemsList变量中,方便后续对列表内容的操作。 - 尝试从
localStorage中读取已有的Tapas项目数据,并将其解析为一个JavaScript数组;如果没有找到相关数据,则初始化为空数组。这个数组被存储在items变量中,作为应用程序内部的数据源。
通过这样的初始化,我们可以确保应用启动时能够正确加载已有数据,并准备好接收用户的新输入。
2.populateList 函数
function populateList(plates, platesList) {
// 更新列表的 HTML 内容
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input
type="checkbox"
data-index="${i}"
id="item${i}"
${plate.done ? 'checked' : ''}
>
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join('');
}
详细解析
参数解释
-
plates: 这是一个数组,其中包含了多个对象,每个对象代表一个Tapas项目。每个对象通常至少包含两个属性:
text: Tapas项目的名称或描述。done: 一个布尔值,表示该项目是否已完成(例如,用户是否已经勾选了对应的复选框)。
-
platesList: 这是一个DOM元素引用,指向页面上的一个无序列表(
<ul>元素),用于展示所有的Tapas项目。
主要步骤
-
遍历
plates数组使用
Array.prototype.map()方法来遍历plates数组。对于数组中的每一个项目(即每个plate),都会执行一次提供的回调函数。这个回调函数接收两个参数:当前项 (plate) 和它的索引 (i)。 -
创建 HTML 字符串
在
map的回调函数内部,我们构建了一个模板字符串(使用反引号`包围),它定义了单个<li>元素的HTML结构。这里有几个关键点:-
模板字符串: 模板字符串允许我们在多行上编写字符串,并且可以方便地嵌入表达式。在本例中,我们使用
${}语法将变量和表达式的值插入到字符串中。 -
复选框 (
<input type="checkbox">) :type="checkbox": 定义这是一个复选框输入。data-index="${i}": 设置自定义属性data-index,存储该Tapas项目在plates数组中的位置。这有助于后续处理,比如更新特定项目的完成状态。id="item${i}": 给每个复选框分配一个唯一的ID,以便与标签关联。${plate.done ? 'checked' : ''}: 如果plate.done为真,则添加checked属性,使复选框默认被选中;否则不添加此属性。
-
标签 (
<label>)for="item${i}":for属性应该与相关联的复选框的id相匹配,这样点击标签也会触发复选框的状态变化。${plate.text}: 显示Tapas项目的名称或描述。
-
-
拼接成完整的 HTML 字符串
map方法返回的是一个新的数组,其中每个元素都是一个<li>元素的HTML字符串。接下来,我们调用.join('')方法将这些字符串连接起来,形成一个单一的、连续的HTML字符串。这样做是为了确保最终的结果没有多余的分隔符。 -
更新 DOM
最后一步是将生成的HTML字符串赋值给
platesList.innerHTML。这会直接替换掉platesList元素内的所有现有内容。浏览器会解析这段HTML字符串,并将其转化为实际的DOM节点,从而更新页面上的Tapas列表。
实际效果
当 populateList 被调用时,它会根据 plates 数组的内容动态生成一系列 <li> 元素,并将它们添加到 platesList 中。如果 plates 数组为空,那么 platesList 将不会有任何子元素。反之,如果有多个项目,那么每个项目都会对应一个 <li> 元素,包含一个复选框和一个标签,显示项目的名称或描述。
这种方法不仅简化了DOM操作,还使得代码更加简洁易读,同时也提高了性能,因为一次性更新整个列表比逐个添加元素要高效得多。
3.addItem 函数
function addItem(event) {
event.preventDefault();
//console.log(this, event.target);
// 性能
const text = (this.querySelector('[name=item]')).value.trim();
// 数据
const item = {
text, // es6 简写 等于 text:text
done: false
}
items.push(item);
populateList(items, itemsList)
localStorage.setItem('items', JSON.stringify(items))// 存储数据 5MB左右的空间
this.reset();
}
在解释这段代码前让我简单介绍event
在JavaScript中,event 参数是事件对象的一个实例,它包含了关于触发事件的详细信息。当某个事件(如点击、提交表单、按键等)被触发时,浏览器会自动创建这个对象,并将其传递给事件处理函数。通过访问 event 对象的属性和方法,开发者可以获取更多关于事件的信息并控制其行为。
事件对象 (event) 的主要用途
- 阻止默认行为:使用
event.preventDefault()方法可以阻止元素的默认行为。 - 停止事件传播:使用
event.stopPropagation()或event.stopImmediatePropagation()方法可以阻止事件冒泡或捕获阶段继续传播。 - 获取触发事件的目标元素:通过
event.target属性可以找到实际触发事件的DOM元素。 - 了解事件类型:
event.type属性返回触发事件的类型(如"click"、"submit"等)。 - 获取键盘或鼠标事件的细节:对于键盘事件(如
keydown),可以使用event.key或event.code获取按下的键;对于鼠标事件(如click),可以使用event.clientX和event.clientY获取鼠标位置。
详细分解
1. event.preventDefault();
- 作用:阻止表单的默认提交行为(即页面刷新)。当用户点击提交按钮时,默认情况下浏览器会尝试将表单数据发送到服务器,并且通常会导致页面重新加载。我们不希望这种情况发生,因为我们希望通过JavaScript动态更新页面内容。
- 为什么重要:如果不调用
preventDefault(),每次用户提交表单后,页面都会重新加载,导致所有之前的状态丢失。通过防止默认行为,我们可以保持当前页面状态不变,同时执行自定义的JavaScript逻辑。
2. console.log(this, event.target);
- 调试信息:打印当前函数上下文 (
this) 和触发事件的目标元素 (event.target)。这有助于开发者了解函数是在什么环境下被执行的,以及哪个具体元素触发了该事件。 this的含义:在事件处理程序中,this指向的是绑定事件监听器的元素,在这里是表单 (<form>元素)。event.target的含义:指向实际触发事件的元素。在表单提交的情况下,通常是提交按钮或回车键触发的事件。
3. const text = (this.querySelector('[name=item]')).value.trim();
- 获取用户输入:使用
querySelector方法找到表单中的[name=item]输入框,并读取其值。trim()方法用于移除字符串两端的空白字符,以确保不会因为多余的空格而导致问题。 querySelector方法:这是一个强大的选择器方法,可以用来查找匹配指定CSS选择器的第一个元素。这里我们使用它来定位表单内的特定输入字段。value属性:获取输入框当前的值,即用户输入的文本。trim()方法:移除字符串两端的空白字符,保证输入的整洁性。
4. const item = { text, done: false };
-
创建新项目对象:这里我们创建了一个包含两个属性的对象:
text: 用户输入的内容。done: 布尔值,表示该项目是否已完成,默认为false。
-
ES6 简写语法:
text等同于text: text,这是ES6引入的一种简化形式,使得代码更加简洁易读。
5. items.push(item);
- 添加新项目:将新创建的
item对象推入items数组中。这意味着我们的本地数据存储已经包含了这个新的Tapas项目。 push方法:数组的一个方法,用于向数组末尾添加一个或多个元素。
6. populateList(items, itemsList);
-
更新UI:调用
populateList函数来根据最新的items数组生成HTML,并更新页面上的无序列表 (<ul>)。这一步确保了用户界面与内部数据保持同步。 -
参数说明:
items: 包含所有Tapas项目的数组。itemsList: 页面上用于展示这些项目的<ul>元素。
7. localStorage.setItem('items', JSON.stringify(items));
- 持久化存储:将更新后的
items数组转换成JSON格式的字符串,并保存到浏览器的localStorage中。这样即使用户关闭或刷新页面,数据也不会丢失。 JSON.stringify方法:将JavaScript对象或数组转换为JSON字符串,以便可以存储在localStorage中,因为localStorage只能存储字符串类型的数据。localStorage.setItem方法:用于设置localStorage中的键值对。第一个参数是要存储的数据项的名称(键),第二个参数是要存储的数据(值)。
8. this.reset();
- 重置表单:调用
reset方法清除表单中的所有字段,使用户能够方便地继续添加新的Tapas项目,而无需手动删除之前的输入。 reset方法:表单元素的一个方法,用于将表单恢复到初始状态,即将所有输入字段清空。
实际效果
每当用户填写表单并点击提交按钮时,addItem 函数会被触发:
- 阻止默认的表单提交行为,防止页面刷新。
- 打印调试信息,帮助开发者理解当前的执行环境。
- 获取用户输入的内容,去除多余空格。
- 创建一个新的Tapas项目对象,包含用户输入和完成状态。
- 将新项目添加到
items数组中,更新内部数据结构。 - 调用
populateList函数,根据最新的items数组更新页面上的列表显示。 - 将更新后的
items数组保存到localStorage中,实现数据的持久化。 - 清空表单,准备接收下一个用户的输入。
通过这种方式,addItem 函数不仅实现了从收集用户输入到更新内部数据结构,再到持久化存储和用户界面刷新的完整流程,还确保了良好的用户体验和数据管理能力。
4.函数调用
addItems.addEventListener('submit', addItem)
populateList(items, itemsList)
1. addItems.addEventListener('submit', addItem)
这行代码为表单元素 (<form>) 添加了一个事件监听器,用于监听“提交”(submit)事件,并指定当该事件触发时应执行的函数——addItem。
解析
-
addEventListener方法:- 这是一个DOM元素的方法,用于注册一个或多个事件类型上的事件处理器。
-
参数解释:
- 第一个参数
'submit':表示要监听的事件类型。在这个例子中,我们监听的是表单的提交事件。 - 第二个参数
addItem:这是当事件发生时应该调用的回调函数。每当用户点击表单中的提交按钮或者按下回车键时,addItem函数就会被调用。
- 第一个参数
实际效果
当你在浏览器中运行这段代码时:
- 每当用户填写表单并尝试提交时(例如通过点击提交按钮或按下回车键),
addItem函数会被触发。 addItem函数负责收集用户输入的数据、创建新的Tapas项目对象、更新内部数据存储,并确保页面上的列表得到相应的刷新。- 由于我们在
addItem函数中使用了event.preventDefault(),所以表单不会按照默认行为提交,而是通过JavaScript动态地更新页面内容。
2. populateList(items, itemsList)
这行代码调用了 populateList 函数,目的是在页面初次加载时根据已经存在的 items 数据来渲染 Tapas 列表。
解析
-
populateList函数:-
这个函数负责根据传入的数据生成HTML,并将这些HTML插入到指定的DOM元素中。它接受两个参数:
plates:这是一个数组,包含了所有需要显示的 Tapas 项目。platesList:这是一个 DOM 元素引用,指向页面上的一个无序列表(<ul>元素),用于展示所有的 Tapas 项目。
-
-
参数传递:
items:这是从localStorage中读取的 Tapas 项目数组,或者是如果localStorage中没有数据时的空数组。itemsList:这是之前通过document.querySelector('.plates')获取到的<ul>元素。
实际效果
当你在浏览器中运行这段代码时:
- 页面加载完成后,
populateList(items, itemsList)立即被调用。 - 它会根据
items数组中的数据动态生成一系列<li>元素,并将它们添加到itemsList中。 - 如果
items数组中有任何项目,那么这些项目将会立即显示在页面上;如果没有,则列表为空。 - 这一步确保了即使用户刷新页面,之前保存的 Tapas 项目仍然会显示出来,提供了良好的用户体验。
结语
通过上述代码解析,我们深入了解了如何利用JavaScript、HTML和localStorage构建一个交互式的Tapas项目管理器。从监听表单提交事件到动态更新页面内容,再到确保数据的持久化存储,每一个步骤都体现了现代Web开发中的最佳实践。addItems.addEventListener('submit', addItem) 和 populateList(items, itemsList) 这两行代码虽然简洁,却承载着实现用户交互与数据展示的核心逻辑。
希望这篇文章能够帮助你掌握这些关键技术点,并启发你在自己的项目中应用类似的模式。无论是创建待办事项列表、购物车功能,还是其他需要保存用户输入的应用场景,这些方法都能为你提供坚实的基础。
如果你在学习过程中遇到了挑战或有任何疑问,请不要犹豫,随时回来查阅本文或继续探索更多资源。编程是一个不断学习和实践的过程,每一次尝试都是进步的机会。感谢你的阅读,期待你在未来的开发旅程中创造出更多精彩的作品!