详细讲解如何在JavaScript基础选择题场景中配合缓存机制,包括题目数据缓存、答题进度保存、缓存更新策略及完整代码示例。
JavaScript基础选择题怎么配合缓存使用
时间:2026-11-08
浏览:299
文章目录
在做JavaScript基础选择题的练习系统或学习页面时,把题目数据和答题进度存到浏览器缓存里,可以减少重复请求、保留用户做题记录,还能支持断线或刷新后继续答题。这篇文章从实际场景出发,讲清楚缓存在选择题场景中的用法、存储方式选择、代码实现和容易出问题的地方。
一、选择题场景中缓存解决什么问题
一个典型的JavaScript选择题页面,数据流程很简单:从服务器拉取题库 → 渲染题目 → 用户选择答案 → 提交或自动判分。问题在于,每次打开页面都重新请求题库,响应慢、浪费流量;用户做了一半刷新页面,进度全部丢失;如果题目顺序随机,刷新后顺序变了,体验更差。
缓存在这里承担两件事:
- 缓存题目数据——把从服务器获取的题库JSON保存到浏览器本地,下次打开直接读取,不必再等请求。
- 缓存答题状态——记录用户当前做到第几题、每题选了什么、哪些已提交,刷新或关闭后能恢复。
这两类缓存的生命周期和更新策略不同,选用的存储方式也不一样,下面分开说明。
二、选择合适的缓存方式
浏览器端常用的缓存手段有 localStorage、sessionStorage 和内存变量。对选择题场景来说,选择依据很明确:
localStorage 适合存题库数据。它在同源下持久保存,关闭浏览器后数据还在,下次打开页面可以直接读取,省去一次网络请求。容量一般是5MB,存几百道选择题的JSON绰绰有余。
sessionStorage 适合存答题进度。它只在当前标签页有效,关掉标签页自动清除。这正好符合"一次答题过程"的生命周期——刷新页面进度还在,关掉页面下次重来。如果需要跨标签页或关闭后仍保留进度,也可以用 localStorage,但要注意主动清理。
内存变量(比如一个普通的JavaScript对象)读写最快,但刷新就没了。它适合在页面运行期间做题目数据的工作副本,配合 localStorage 一起使用。
localStorage 和 sessionStorage 只能存字符串。存入对象或数组时必须用 JSON.stringify() 序列化,取出时用 JSON.parse() 反序列化。忘记这一步是最常见的错误。
三、缓存题库数据的实现
核心逻辑是:打开页面时先检查缓存中有没有题库,有就直接用,没有就请求接口再存进缓存。为了让题库能在服务器更新后同步,还要给缓存加一个版本号或过期时间。
下面是一个完整示例。题库数据的格式是一个数组,每个元素代表一道选择题:
// 题库数据结构示例
const sampleQuiz = [
{
id: 1,
question: "typeof null 的返回值是什么?",
options: ["null", "undefined", "object", "string"],
answer: 2 // 正确答案的索引
},
{
id: 2,
question: "以下哪个方法不会改变原数组?",
options: ["push", "splice", "concat", "sort"],
answer: 2
}
];
缓存读写的封装函数:
const CACHE_KEY = 'quiz_data';
const CACHE_VERSION_KEY = 'quiz_version';
const CURRENT_VERSION = '2024-01-15'; // 和服务器约定的版本号
// 从缓存读取题库
function getQuizFromCache() {
const version = localStorage.getItem(CACHE_VERSION_KEY);
if (version !== CURRENT_VERSION) {
// 版本不匹配,缓存已过期
localStorage.removeItem(CACHE_KEY);
return null;
}
const data = localStorage.getItem(CACHE_KEY);
if (!data) return null;
try {
return JSON.parse(data);
} catch (e) {
// JSON损坏,清除重来
localStorage.removeItem(CACHE_KEY);
return null;
}
}
// 写入缓存
function saveQuizToCache(quizArray) {
localStorage.setItem(CACHE_KEY, JSON.stringify(quizArray));
localStorage.setItem(CACHE_VERSION_KEY, CURRENT_VERSION);
}
页面初始化时调用:
async function loadQuiz() {
let quiz = getQuizFromCache();
if (quiz) {
console.log('从缓存加载题库,共', quiz.length, '道题');
return quiz;
}
// 缓存没有,请求服务器
const response = await fetch('/api/quiz/javascript-basic');
quiz = await response.json();
saveQuizToCache(quiz);
console.log('从服务器加载并缓存题库');
return quiz;
}
// 使用
loadQuiz().then(quiz => {
renderQuestion(quiz, 0); // 渲染第一题
});
这里的版本号 CURRENT_VERSION 可以写死在前端代码中(每次部署更新),也可以在页面加载时先请求一个轻量的版本号接口,拿到版本后再决定是否使用缓存。后者更灵活,但多了一次请求。
四、缓存答题进度的实现
答题进度需要记录的信息包括:当前做到第几题(索引值)、每道题用户选择了哪个选项。用 sessionStorage 存储,页面刷新后读取恢复。
const PROGRESS_KEY = 'quiz_progress';
// 读取进度
function getProgress() {
const data = sessionStorage.getItem(PROGRESS_KEY);
if (!data) {
return { currentIndex: 0, answers: {} };
}
try {
return JSON.parse(data);
} catch (e) {
return { currentIndex: 0, answers: {} };
}
}
// 保存进度(每次用户选择答案时调用)
function saveProgress(currentIndex, answers) {
const progress = { currentIndex, answers };
sessionStorage.setItem(PROGRESS_KEY, JSON.stringify(progress));
}
// 清除进度(答题完成后调用)
function clearProgress() {
sessionStorage.removeItem(PROGRESS_KEY);
}
answers 用一个对象存储,键是题目id,值是用户选择的选项索引。比如 { "1": 2, "2": 0 } 表示第1题选了C(索引2),第2题选了A(索引0)。
把这些组合进题目渲染和切换逻辑里:
let quizData = [];
let progress = getProgress();
async function init() {
quizData = await loadQuiz();
progress = getProgress();
renderQuestion(quizData, progress.currentIndex);
}
function renderQuestion(quiz, index) {
const q = quiz[index];
const container = document.getElementById('quiz-container');
// 题目文本
let html = '<h3>第' + (index + 1) + '题:' + q.question + '</h3>';
// 选项
q.options.forEach((opt, i) => {
const checked = progress.answers[q.id] === i ? 'checked' : '';
html += '<label><input type="radio" name="q' + q.id + '" value="' + i + '" ' + checked + '> '
+ String.fromCharCode(65 + i) + '. ' + opt + '</label><br>';
});
container.innerHTML = html;
}
// 用户选择答案
document.addEventListener('change', function(e) {
if (e.target.type === 'radio') {
const qId = quizData[progress.currentIndex].id;
progress.answers[qId] = parseInt(e.target.value);
saveProgress(progress.currentIndex, progress.answers);
}
});
// 下一题
function nextQuestion() {
if (progress.currentIndex < quizData.length - 1) {
progress.currentIndex++;
saveProgress(progress.currentIndex, progress.answers);
renderQuestion(quizData, progress.currentIndex);
}
}
五、缓存更新与过期策略
题库数据不是一成不变的。服务器可能增加新题、修改错误选项或调整答案。缓存如果一直不更新,用户就会看到旧数据。常见的过期策略有三种:
版本号比对:前端代码中写入一个版本号常量,每次部署新版本时更新。页面加载时比对缓存中的版本号,不一致就清除缓存重新请求。前面代码示例用的就是这种方式。优点是简单可控,缺点是必须改代码才能触发更新。
时间戳过期:缓存时同时保存当前时间戳,读取时检查是否超过设定的有效期(比如24小时)。
function saveWithExpiry(key, data, ttlMs) {
const item = {
value: data,
expiry: Date.now() + ttlMs
};
localStorage.setItem(key, JSON.stringify(item));
}
function getWithExpiry(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
try {
const item = JSON.parse(raw);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
} catch (e) {
localStorage.removeItem(key);
return null;
}
}
// 使用:缓存24小时
saveWithExpiry('quiz_data', quizArray, 24 * 60 * 60 * 1000);
后台静默更新:先用缓存数据渲染页面让用户开始答题,同时在后台请求最新数据。如果发现数据有变化,在用户完成当前答题后再更新缓存,下次加载生效。这种方式体验最好,但实现稍微复杂一些。
六、容易出错的地方
存储容量溢出:localStorage 写入超出容量限制时会抛出异常。写入操作应该用 try...catch 包裹:
try {
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
} catch (e) {
console.warn('缓存写入失败,可能超出容量限制', e);
// 降级:清除旧缓存后重试,或直接使用内存数据
localStorage.clear();
}
隐私模式限制:部分浏览器在隐私/无痕模式下会限制 localStorage 的写入(Safari早期版本会直接抛异常)。代码中应考虑缓存不可用的情况,回退到内存变量。
JSON解析失败:用户手动修改了 localStorage 中的数据,或者存入了非JSON字符串,JSON.parse() 会抛错。每次读取都要用 try...catch 保护,解析失败就清除脏数据,从服务器重新获取。
多标签页冲突:如果用户同时在两个标签页答题,两边都往 localStorage 写入进度,后写入的会覆盖先写入的。解决办法是给每次答题会话生成一个唯一ID,或者用 sessionStorage(天然隔离标签页)。
缓存与题目顺序:如果题目每次随机排序,缓存进度时要同时缓存当次的题目顺序(或者直接缓存排序后的题目数组),否则恢复进度后题目顺序变了,之前的选择就对不上了。
七、完整工作流程总结
把前面的内容串起来,一个配合缓存使用的JavaScript选择题页面完整流程如下:
- 页面加载,检查
localStorage中有无题库缓存且未过期 → 有就直接解析使用,没有就请求服务器获取并写入缓存。 - 检查
sessionStorage中有无答题进度 → 有就恢复到对应题目和已选答案,没有就从第1题开始。 - 用户每选择一个答案,立即将进度写入
sessionStorage。 - 用户切换题目时同步更新进度中的
currentIndex。 - 全部答完提交后,清除
sessionStorage中的进度。 - 下次访问时,题库从缓存加载(如果版本匹配),答题进度从零开始。
选择题和缓存的配合并不复杂,关键是分清"题库数据"和"答题进度"两种缓存的职责和生命周期。题库用 localStorage 持久存储加版本控制,答题进度用 sessionStorage 随会话销毁,两者配合就能覆盖绝大多数场景。写入时记得处理异常、解析时记得做容错,把这些细节做到位,缓存就能稳定地服务于选择题功能。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。