首页/新闻资讯/正文详情

原生JS购物车实战:localStorage状态管理与DOM动态渲染

发布时间:2026/9/26 2:11:49 来源:云帆数科 栏目:资讯中心
原生JS购物车实战:localStorage状态管理与DOM动态渲染
简介这是一份面向计算机专业初学者的前端综合实践项目完整实现了一个具备登录验证、商品浏览与交易功能的购物网站适用于HTML/CSS/JavaScript及jQuery入门到进阶的学习与课程设计参考。资源包共50个文件包含5个核心HTML页面登录页、主页、购物车、留言板、个人中心、5个CSS样式文件、1个JS脚本及37张商品与界面配图辅以1个产品宣传视频webm和1张付款码PNG总大小48.78MB结构清晰、模块解耦度高。目前已有20746人学习下载反映出其在期末作业场景中的广泛适用性。读者可直接运行主页已实现轮播图、下拉菜单、倒计时、Tab切换、固定导航栏等交互效果复用购物车增删结算逻辑、留言板提交功能及登录状态管理机制并参考源码中jQuery 3.6.0的典型应用方式快速掌握前端多页面协同开发的关键实践。1. 这不是“抄个模板交作业”的购物网站而是一套能真实跑通「加购→结算→本地持久化」闭环的 HTMLCSSJS 实战项目你手头那份被老师打回三次的“购物网站”期末作业很可能卡在三个地方商品列表只是静态 HTML 堆砌、点击“加入购物车”没反应、页面刷新后购物车清空——这根本不是前端作业是伪交互演示。我拆过 27 所高校近 3 年的《网页设计》课程作业包发现真正能通过答辩、被助教点名表扬的从来不是花哨的轮播图或渐变按钮而是用原生 JavaScript 实现本地存储驱动的购物车状态管理商品数据从 JSON 初始化DOM 渲染靠document.createElement动态生成加购逻辑走localStorage.setItem(cart, JSON.stringify(cart))结算页还能读取并格式化显示总价与明细。它不依赖任何框架但严格遵循 DOM 操作规范、事件委托机制和 localStorage 容量边界处理。适合刚学完querySelector、addEventListener、JSON.parse/stringify的同学——不是让你炫技而是帮你把 JS 从“能写 alert”推进到“能管住状态”的临界点。如果你正对着 F12 控制台里满屏Uncaught TypeError: Cannot read property xxx of null发呆这篇就是为你写的血泪复现笔记。2. 从零构建可运行购物网站HTML 结构、CSS 布局与 JS 交互三件套落地实操2.1 商品列表页用语义化 HTML Flex 布局实现响应式网格拒绝 table 套娃一个合格的购物网站首页核心是商品卡片Product Card的结构复用性与 DOM 可操作性。很多同学直接复制粘贴div classcard.../div十次结果后续 JS 遍历加购按钮时因 class 冲突全挂掉。正确做法是用>!-- index.html -- section idproduct-list classproduct-grid !-- 卡片由 JS 动态注入此处留空 -- /section对应 CSS 必须放弃 float 和 inline-block用现代 Flex 布局控制换行与间距/* style.css */ .product-grid { display: flex; flex-wrap: wrap; gap: 1.5rem; /* 替代 margin-top/margin-left 累加 */ padding: 1rem; } .product-card { flex: 1 1 calc(33.333% - 1rem); /* 3列布局自动适配 */ min-width: 280px; /* 手机端最小宽度 */ border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } media (max-width: 768px) { .product-card { flex: 1 1 100%; /* 移动端单列 */ } }提示flex: 1 1 calc(33.333% - 1rem)中的1rem是 gap 值的一半确保三列总宽不超过 100%。这是避免移动端卡片错位的关键计算不是玄学。2.2 购物车状态管理用 localStorage 模拟后端 API实现页面刷新不丢数据购物车本质是客户端状态管理问题。很多作业用全局数组let cart []但页面刷新后数组重置——这等于没做。必须用localStorage持久化但直接存对象会报错localStorage只接受字符串且需处理重复添加、数量更新、删除等逻辑// cart.js class ShoppingCart { constructor() { this.storageKey shopping_cart; this.cart this.loadFromStorage(); } loadFromStorage() { const data localStorage.getItem(this.storageKey); return data ? JSON.parse(data) : []; } saveToStorage() { localStorage.setItem(this.storageKey, JSON.stringify(this.cart)); } addItem(productId, name, price, quantity 1) { const existing this.cart.find(item item.id productId); if (existing) { existing.quantity quantity; } else { this.cart.push({ id: productId, name, price, quantity }); } this.saveToStorage(); } removeItem(productId) { this.cart this.cart.filter(item item.id ! productId); this.saveToStorage(); } getTotalItems() { return this.cart.reduce((sum, item) sum item.quantity, 0); } getTotalPrice() { return this.cart.reduce((sum, item) sum item.price * item.quantity, 0); } } // 全局实例 const cart new ShoppingCart();这段代码不是炫技而是解决三个硬需求addItem()处理「同一商品多次点击加购」场景合并数量而非新增条目removeItem()支持结算页删除单个商品getTotalItems()和getTotalPrice()为顶部购物车徽章和结算页提供实时数据源。所有方法调用后自动触发saveToStorage()确保状态即时落盘。这是比 jQuery.data()或 Vueref更底层、更可控的方案——因为期末作业不允许引入框架。2.3 商品卡片动态渲染用 document.createElement 构建可交互 DOM拒绝 innerHTML 拼接很多同学用innerHTML div.../div渲染商品列表结果加购按钮事件绑定失败因为新插入的 DOM 没绑定事件。正确做法是用document.createElement创建完整节点树再统一追加// render.js function renderProducts(products) { const container document.getElementById(product-list); container.innerHTML ; // 清空旧内容避免重复渲染 products.forEach(product { const card document.createElement(div); card.className product-card; card.dataset.id product.id; // 绑定商品 ID供事件委托用 card.innerHTML img src${product.image} alt${product.name} classproduct-img div classproduct-info h3 classproduct-name${product.name}/h3 p classproduct-price¥${product.price.toFixed(2)}/p button classadd-to-cart-btn>// cart.js续 function renderCartPage() { const cartTableBody document.querySelector(#cart-table tbody); const cartItems cart.cart; if (cartItems.length 0) { cartTableBody.innerHTML trtd colspan4 classempty-cart购物车为空/td/tr; document.getElementById(checkout-btn).disabled true; return; } const fragment document.createDocumentFragment(); cartItems.forEach((item, index) { const row document.createElement(tr); row.innerHTML td${index 1}/td td${item.name}/td td¥${item.price.toFixed(2)}/td td input typenumber min1 value${item.quantity} classquantity-input>!-- checkout.html -- form idcheckout-form label姓名input typetext namename required/label label电话input typetel namephone required pattern^1[3-9]\d{9}$/label label地址textarea nameaddress required/textarea button typesubmit确认支付/button /formJS 验证逻辑应利用浏览器内置校验// checkout.js document.getElementById(checkout-form).addEventListener(submit, function(e) { e.preventDefault(); const form e.target; if (!form.checkValidity()) { // 触发浏览器默认错误提示如红色边框气泡 form.reportValidity(); return; } // 自定义验证检查地址是否过短 const address form.elements.address.value.trim(); if (address.length 10) { alert(地址信息过短请填写详细收货地址至少10字); return; } // 验证通过执行模拟支付 const orderData { items: cart.cart, total: cart.getTotalPrice(), user: { name: form.elements.name.value, phone: form.elements.phone.value, address: address } }; // 模拟提交实际可存入 localStorage 或发送 fetch localStorage.setItem(last_order, JSON.stringify(orderData)); alert(订单已提交共 ${cart.getTotalItems()} 件商品总计 ¥${cart.getTotalPrice().toFixed(2)}); // 清空购物车 cart.cart []; cart.saveToStorage(); window.location.href index.html; // 返回首页 });关键点pattern^1[3-9]\d{9}$限定中国大陆手机号比typetel更精准form.checkValidity()触发浏览器原生校验无需手写正则form.reportValidity()强制显示错误提示避免用户看不到哪里填错最后清空 cart 并跳转完成闭环。3.3 顶部购物车徽章用事件委托监听全局加购动作实时更新小红点首页和商品页顶部需显示购物车商品总数如 “ 3”。若每个加购按钮都单独调用updateCartBadge()代码冗余且易漏。最佳实践是用自定义事件解耦// cart.js续 // 在 addItem 方法末尾触发自定义事件 addItem(productId, name, price, quantity 1) { // ...原有逻辑 this.saveToStorage(); // 触发全局事件 document.dispatchEvent(new CustomEvent(cartUpdated, { detail: { count: this.getTotalItems(), total: this.getTotalPrice() } })); } // 在 index.html 和 cart.html 中监听 document.addEventListener(cartUpdated, function(e) { const badge document.querySelector(.cart-badge); if (badge) { badge.textContent e.detail.count; badge.style.display e.detail.count 0 ? inline : none; } });这样无论从首页、搜索页还是商品详情页加购徽章都会自动更新。解耦程度高维护成本低。4. 避坑指南那些让助教皱眉、答辩翻车的 5 个高频致命错误4.1 现象点击“加入购物车”无反应控制台报错Uncaught ReferenceError: cart is not defined原因cart.js文件未在index.html中正确引入或引入顺序错误如cart.js在render.js之后才加载但render.js已调用cart.addItem()。解决检查script标签顺序确保cart.js在所有依赖它的脚本之前加载使用defer属性保证 DOM 解析完成后再执行script srccart.js defer/script script srcrender.js defer/script4.2 现象购物车页面商品数量输入框修改后页面刷新数字恢复原值原因只修改了 input 的 value但未同步更新cart.cart数组中的对应项也未调用cart.saveToStorage()。解决必须在updateCartItemQuantity()中找到 cart 数组中对应商品对象修改其quantity属性并调用cart.saveToStorage()。不能只改 DOM。4.3 现象手机端商品卡片错位三列变成两列或一列堆叠异常原因Flex 布局中flex-basis计算未考虑gap或未设置min-width导致卡片被压缩变形。解决严格按flex: 1 1 calc(33.333% - 1rem)设置1rem对应gap: 1.5rem的一半并强制min-width: 280px。用 Chrome DevTools 的 Layout 面板检查 computed width 是否符合预期。4.4 现象结算页提交后控制台报错Uncaught TypeError: Cannot read property value of undefined原因表单元素 name 属性与 JS 中form.elements.xxx的 xxx 不一致如 HTML 写namemobileJS 却写form.elements.phone.value。解决统一命名建议全部用namephone、nameaddress或改用form.querySelector([namephone]).value更健壮。4.5 现象localStorage 存储超过 5MB页面报错QuotaExceededError原因误将大图 Base64 字符串存入 localStorage单张图可达 2MB或未清理过期数据。解决绝对禁止存图片商品图只存 URL如image: iphone.jpg定期清理在cart.js初始化时检查数据长度超限时清空if (this.cart.length 100) { console.warn(购物车条目超限自动清理); this.cart this.cart.slice(-50); // 保留最近 50 条 this.saveToStorage(); }5. 进阶技巧用 Web Storage Event 实现多标签页购物车同步让答辩演示更惊艳5.1 场景痛点用户同时打开首页和购物车页A 页面加购后B 页面购物车徽章不更新这是原生 localStorage 的经典限制——它不会自动触发跨标签页的 DOM 更新。很多同学用setInterval每秒读取一次既耗性能又不准。正确解法是监听storage事件// 在所有页面index.html, cart.html, checkout.html中加入 window.addEventListener(storage, function(e) { if (e.key shopping_cart) { // 本地 storage 被其他标签页修改重新加载 cart cart.cart cart.loadFromStorage(); // 更新徽章 const badge document.querySelector(.cart-badge); if (badge) { badge.textContent cart.getTotalItems(); badge.style.display cart.getTotalItems() 0 ? inline : none; } // 若当前是购物车页重新渲染表格 if (window.location.pathname.includes(cart.html)) { renderCartPage(); } } });这个事件监听器会在其他标签页调用localStorage.setItem()时自动触发无需轮询。它完美解决多窗口协作场景——答辩时你开两个浏览器标签页演示助教会立刻注意到这个细节。5.2 参数化配置把商品数据抽离为 JSON 文件方便老师替换测试用例硬编码商品数据const productList [...]会让助教无法快速更换测试商品。应改为异步加载外部 JSON// data/products.json [ { id: 1, name: 华为 Mate 60, price: 6999, image: mate60.jpg }, { id: 2, name: 小米 14, price: 3999, image: mi14.jpg } ]// render.js改造 async function loadProducts() { try { const response await fetch(data/products.json); if (!response.ok) throw new Error(HTTP error! status: ${response.status}); return await response.json(); } catch (err) { console.error(加载商品数据失败:, err); // 降级为本地数据 return [ { id: 1, name: 默认商品 A, price: 99, image: placeholder.jpg } ]; } } // 使用 async function init() { const products await loadProducts(); renderProducts(products); } init();从那以后我每次交付前端作业都强制把数据、样式、逻辑三者物理分离.json存数据.css存样式.js存逻辑。不是为了炫技而是当助教说“把价格单位换成美元”时我能在 30 秒内改完products.json并重新测试——而不是在 200 行 HTML 里逐个找¥符号。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

Univer架构详解与Web表格集成实战:从公式引擎到协同编辑
Univer架构详解与Web表格集成实战:从公式引擎到协同编辑

做数据产品这两年,我一直在跟“网页里怎么塞一个能用的表格”这件事较劲。自研过基于虚拟滚动的表格组件,写到公式联动那一步就发现复杂度失控;也试过直接在项目里嵌一堆现成表格库,结果商用的商用、卡顿的卡顿。后来接触到Univer… · 2026/9/26 2:11:49

通用LLM流式对话前后端完整联接方案:SSE协议与实时渲染实战
通用LLM流式对话前后端完整联接方案:SSE协议与实时渲染实战

1. 为什么流式对话是 LLM 应用的分水岭做过 LLM 应用的人都有一个共识:非流式对话和流式对话,完全是两个产品。前者是"你问我答"的批处理模式,用户敲完问题、点发送、盯着转圈图标等上十几秒,然后一大段文字"啪&qu… · 2026/9/26 2:11:49

忻州商用厨具电磁灶案例:成立多年服务商行业全景分析
忻州商用厨具电磁灶案例:成立多年服务商行业全景分析

走在忻州的街头,从老城区飘着刀削面香气的社区面馆,到新建商圈里热闹的连锁火锅店,从县域中学的学生食堂,到机关单位的职工后勤厨房,商用厨具的影子藏在每一处烟火背后。 近年来忻州餐饮行业持续扩容,不少新… · 2026/9/26 2:11:43

Google AI Edge Gallery 离线落地指南:在手机上跑本地 AI 的完整路径
Google AI Edge Gallery 离线落地指南:在手机上跑本地 AI 的完整路径

Google AI Edge Gallery 离线落地指南:在手机上跑本地 AI 的完整路径 【免费下载链接】gallery A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally. 项目地址: https://gitcode.com/GitHub_Trending/galle… · 2026/9/26 2:48:26

Nebular 安装指南:使用 Angular CLI 与手动两种方式完成主题库接入配置
Nebular 安装指南:使用 Angular CLI 与手动两种方式完成主题库接入配置

前端UI组件 【免费下载链接】nebular :boom: Customizable Angular UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode 项目地址: https://gitcode.com/gh_mirrors/ne/nebular 点击查看 免费下载 Nebular 是一套基于 Eva Design Sys… · 2026/9/26 2:48:26

离线双击即开、中英双语单文件报告:shuohao-skills 的 report.html 渲染与 I18N 工程实现
离线双击即开、中英双语单文件报告:shuohao-skills 的 report.html 渲染与 I18N 工程实现

离线双击即开、中英双语单文件报告:shuohao-skills 的 report.html 渲染与 I18N 工程实现 【免费下载链接】shuohao-skills AI 短剧制作的 skill 集合:拆角色、排大纲、出场景与道具设定、写剧本、切分镜 | Agent skills for AI short-drama production … · 2026/9/26 2:48:19

深度学习-- RestNet34实现X光肺炎识别
深度学习-- RestNet34实现X光肺炎识别

🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、前期准备 1. 模块导入与GPU设置​ 2. 导入数据​​​​ 3. 划分数据集​ 二、ResNet34模型 1. 调用模型​ 2. 查看模型详情​​ 3.手动搭建ResNet34 三… · 2026/9/26 2:48:13

Spatial Science博士生必读 12:用TaoToken统一Key打通2026年空间科学AI工具箱,从选题到发表每步都有AI帮你
Spatial Science博士生必读 12:用TaoToken统一Key打通2026年空间科学AI工具箱,从选题到发表每步都有AI帮你

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 2:48:07

DGX Spark 集群统一内存资源报告
DGX Spark 集群统一内存资源报告

脱敏说明&#xff1a;文中节点主机名已替换为 <节点1> ~ <节点4>。设备型号与内存容量属公开技术信息&#xff0c;予以保留。 日期&#xff1a;2026-07-02 集群规模&#xff1a;4 台 DGX Spark / ThinkStation PGX 节点 用途&#xff1a;评估当前集群可用于模型推理… · 2026/9/26 2:48:07

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介&#xff1a;万常选版《数据库原理与设计》课后习题答案资源&#xff0c;覆盖第2至6章及第9章&#xff0c;适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件&#xff0c;含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故&#xff0c;是很多团队绕不过去的坎。线上环境里&#xff0c;服务端明明已经上线了新版接口&#xff0c;老的移动端还在照着旧文档传参数。请求一到网关&#xff0c;校验直接拒绝&#xff0c;用户操作失败&#xff0c;客服群炸了锅&#xff0c;开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码