
本文详解在 Todo 列表应用中,因误用 span.value 导致 localStorage 存储为 null 的根本原因,并提供基于 innerHTML(或更优的 textContent)的修复方案,确保任务文本准确保存与恢复。
本文详解在 todo 列表应用中,因误用 `span.value` 导致 localstorage 存储为 `null` 的根本原因,并提供基于 `innerhtml`(或更优的 `textcontent`)的修复方案,确保任务文本准确保存与恢复。
在构建基于浏览器本地存储的 Todo 应用时,一个常见却易被忽视的错误是:试图通过 .value 属性读取 元素的内容。由于 是纯内容容器元素(phrasing content),它不具有 value 属性——该属性仅存在于表单控件(如 、
✅ 正确做法:使用 textContent(推荐)或 innerHTML
应改用语义更清晰、性能更优且安全的 textContent 属性来提取纯文本内容:
function save() {
const liItems = document.querySelectorAll('li span');
const data = [];
liItems.forEach(span => {
data.push(span.textContent); // ✅ 安全、高效、无 HTML 注入风险
});
localStorage.setItem('todoItems', JSON.stringify(data));
console.log('Saved to localStorage:', data);
}? 为什么推荐 textContent 而非 innerHTML?
- textContent 仅返回标签内的纯文本(自动转义 HTML),避免 XSS 风险;
- innerHTML 会返回原始 HTML 字符串(如含 标签则一并返回),在 Todo 场景中通常不需要;
- textContent 性能更好,且语义上更符合“获取待办事项文字”的意图。
? 完整修复后的关键逻辑(含初始化优化)
以下是整合修复点后的精简可靠版本(已修正变量作用域、空值处理及执行时机):
const itemInput = document.querySelector('#item');
const toDoList = document.querySelector('ul');
// 添加新任务
const addToDo = (text = '') => {
if (!text.trim()) return; // 忽略空输入
const li = document.createElement('li');
li.innerHTML = `
<span>${escapeHtml(text)}</span>
<i class="fas fa-times"></i>
`;
toDoList.appendChild(li);
// 点击切换完成态
li.addEventListener('click', () => li.classList.toggle('dark'));
// 删除按钮
li.querySelector('i').addEventListener('click', () => li.remove());
save(); // 实时保存
};
// HTML 转义(防御 XSS,尤其配合 innerHTML 使用)
const escapeHtml = (str) => str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
// 保存所有 span 文本到 localStorage
const save = () => {
const spans = document.querySelectorAll('li span');
const data = Array.from(spans).map(span => span.textContent);
localStorage.setItem('todoItems', JSON.stringify(data));
};
// 页面加载时恢复数据
document.addEventListener('DOMContentLoaded', () => {
const saved = localStorage.getItem('todoItems');
if (saved) {
try {
const items = JSON.parse(saved);
items.forEach(item => addToDo(item));
} catch (e) {
console.warn('Failed to parse localStorage data:', e);
localStorage.removeItem('todoItems'); // 清理损坏数据
}
}
});
// 回车提交
itemInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
addToDo(itemInput.value);
itemInput.value = '';
}
});⚠️ 注意事项与最佳实践
- 避免全局变量:原代码中 data = [] 是隐式全局变量,应声明为 const data = [] 或直接内联使用(如示例所示);
- DOMContentLoaded 替代 IIFE:使用事件监听替代立即执行函数,确保 DOM 已就绪;
- 异常防护:JSON.parse() 必须包裹 try...catch,防止 localStorage 数据损坏导致脚本中断;
- 空值校验:在 addToDo() 中检查 text.trim(),避免生成空
- ;
- XSS 防御:若用户输入可能含 HTML,务必对插入内容做转义(如 escapeHtml 函数),切勿直接拼接未过滤的用户输入到 innerHTML。
通过以上修正,你的 Todo 应用将稳定地将真实任务文本存入 localStorage,并在刷新后精准还原——不再出现 "null" 占位符问题。










