JavaScript基础选择题怎么配合缓存使用

作者:窗边听雨

时间:2026-11-08

浏览:299

详细讲解如何在JavaScript基础选择题场景中配合缓存机制,包括题目数据缓存、答题进度保存、缓存更新策略及完整代码示例。

文章目录

在做JavaScript基础选择题的练习系统或学习页面时,把题目数据和答题进度存到浏览器缓存里,可以减少重复请求、保留用户做题记录,还能支持断线或刷新后继续答题。这篇文章从实际场景出发,讲清楚缓存在选择题场景中的用法、存储方式选择、代码实现和容易出问题的地方。

一、选择题场景中缓存解决什么问题

一个典型的JavaScript选择题页面,数据流程很简单:从服务器拉取题库 → 渲染题目 → 用户选择答案 → 提交或自动判分。问题在于,每次打开页面都重新请求题库,响应慢、浪费流量;用户做了一半刷新页面,进度全部丢失;如果题目顺序随机,刷新后顺序变了,体验更差。

缓存在这里承担两件事:

  • 缓存题目数据——把从服务器获取的题库JSON保存到浏览器本地,下次打开直接读取,不必再等请求。
  • 缓存答题状态——记录用户当前做到第几题、每题选了什么、哪些已提交,刷新或关闭后能恢复。

这两类缓存的生命周期和更新策略不同,选用的存储方式也不一样,下面分开说明。

JavaScript基础选择题怎么配合缓存使用
图1:选择题系统中缓存所处的位置及数据流向

二、选择合适的缓存方式

浏览器端常用的缓存手段有 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);
  }
}
JavaScript基础选择题怎么配合缓存使用
图2:答题进度的缓存读写步骤

五、缓存更新与过期策略

题库数据不是一成不变的。服务器可能增加新题、修改错误选项或调整答案。缓存如果一直不更新,用户就会看到旧数据。常见的过期策略有三种:

版本号比对:前端代码中写入一个版本号常量,每次部署新版本时更新。页面加载时比对缓存中的版本号,不一致就清除缓存重新请求。前面代码示例用的就是这种方式。优点是简单可控,缺点是必须改代码才能触发更新。

时间戳过期:缓存时同时保存当前时间戳,读取时检查是否超过设定的有效期(比如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);

后台静默更新:先用缓存数据渲染页面让用户开始答题,同时在后台请求最新数据。如果发现数据有变化,在用户完成当前答题后再更新缓存,下次加载生效。这种方式体验最好,但实现稍微复杂一些。

JavaScript基础选择题怎么配合缓存使用
图3:三种缓存更新策略的触发时机、优缺点对比

六、容易出错的地方

存储容量溢出: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选择题页面完整流程如下:

  1. 页面加载,检查 localStorage 中有无题库缓存且未过期 → 有就直接解析使用,没有就请求服务器获取并写入缓存。
  2. 检查 sessionStorage 中有无答题进度 → 有就恢复到对应题目和已选答案,没有就从第1题开始。
  3. 用户每选择一个答案,立即将进度写入 sessionStorage。
  4. 用户切换题目时同步更新进度中的 currentIndex。
  5. 全部答完提交后,清除 sessionStorage 中的进度。
  6. 下次访问时,题库从缓存加载(如果版本匹配),答题进度从零开始。
JavaScript基础选择题怎么配合缓存使用
图4:选择题配合缓存的完整工作流程总结

选择题和缓存的配合并不复杂,关键是分清"题库数据"和"答题进度"两种缓存的职责和生命周期。题库用 localStorage 持久存储加版本控制,答题进度用 sessionStorage 随会话销毁,两者配合就能覆盖绝大多数场景。写入时记得处理异常、解析时记得做容错,把这些细节做到位,缓存就能稳定地服务于选择题功能。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章