☰
1号店电商前端JS代码解析:原生JavaScript购物车与商品列表实战
2026/10/7 16:38:46 网站建设 项目流程

简介:这份资源是一套基于JavaScript的电商网站前端实现代码,模拟“1号店”与京东合作场景下的核心交互功能,适合前端初学者或电商项目开发者参考学习。完整包内共347个文件,包含7个JS脚本、6个CSS样式、6个HTML页面及大量图片素材,其中图片以jpg、png、gif为主,构成商品展示、轮播动效与界面装饰所需的视觉资源;代码覆盖购物车增删、用户注册验证、密码修改等典型电商模块,可帮助理解DOM操作、事件监听、异步请求及表单校验的实际应用。资源包大小约3.04MB,目录按页面拆分为首页、分类列表、商品详情、注册页等独立模块,还附带IDE工作区配置,便于直接导入工程查看。所有页面均配有对应样式与脚本,整体结构清晰易读。目前已有3423人学习下载,适合想通过完整项目源码快速上手电商前端开发、深入学习原生JS交互逻辑的读者。

1. 1号店完整代码:一套能直接跑起来的电商前端 JS 项目

做电商前端的人手里肯定都存过几份「经典老代码」。这套 1号店完整代码,就是从京东1号店早期页面里整理出来的前端项目包,里面的 JS 代码完整覆盖了商品列表、购物车、结算页、用户中心这些电商标配模块。它最实用的地方在于:不依赖 Vue、React 这类框架,纯原生 HTML + CSS + JavaScript,打开就能看,改一改就能用。我建议两类人重点关注:刚入行想搞懂电商页面数据流的新手,以及做活动页、H5 商城时想快速抄作业的老手。这份资源能帮你把「JS 代码如何驱动一个完整的电商页面」这件事彻底看明白。

2. 解开代码结构:从 HTML 入口到 JS 模块的调用关系

拿到代码包第一步不是双击 index.html,而是先把目录结构摸清楚。老式电商前端项目的组织方式大同小异:一个入口页面,多个 JS 文件通过 script 标签按顺序引入,外层挂公共样式,图片和字体单独放目录。搞懂这套组织方式,后面所有排查工作都顺了。

2.1 目录结构与入口文件的加载顺序

拆开压缩包后,你会看到类似下面的目录结构:

yihaodian/ ├── css/ │ ├── base.css │ ├── index.css │ └── cart.css ├── js/ │ ├── common.js │ ├── goods.js │ ├── cart.js │ └── user.js ├── images/ ├── index.html └── cart.html

先别急着改任何文件,打开 index.html 看 head 里的 link 标签和底部 body 结束前的 script 标签。加载顺序是严格按照「样式先加载,脚本按依赖后加载」的方式写的。以 common.js 为例,它里面定义了全局的window.YHD命名空间,后续所有模块都挂在这个对象下面,所以它必须放在最前面:

<!-- 正确顺序 --> <link rel="stylesheet" href="css/base.css"> <link rel="stylesheet" href="css/index.css"> ... <script src="js/common.js"></script> <script src="js/goods.js"></script> <script src="js/cart.js"></script>

这里有个关键逻辑:goods.js里的函数会调用common.js里定义的YHD.formatPrice(),如果顺序反了,浏览器执行到goods.js时会直接抛YHD is not defined,整个页面的脚本都会中断。所以你在调整目录结构时,script 标签的顺序必须保持依赖在前、被依赖在后。

如果你用的是 VS Code,可以直接装一个 Live Server 插件来跑这套代码。右击 index.html 选择 Open with Live Server,它会在本地起一个 5500 端口的 HTTP 服务,这样页面里的相对路径资源都能正常加载。

2.2 JS 模块划分:商品、购物车、订单、用户四大域

这套代码把功能拆得很清楚,每个 JS 文件只负责一类职责。虽然没有什么模块化规范,但这种朴素的文件划分方式反而更容易读懂业务边界。

文件职责暴露的顶层方法
common.js公共工具:价格格式化、URL 参数解析、消息弹层YHD.formatPriceYHD.getQuery
goods.js商品列表渲染、筛选、排序YHD.Goods.renderYHD.Goods.filter
cart.js购物车加减、删除、总价计算YHD.Cart.addYHD.Cart.updateTotal
user.js登录状态展示、用户信息回填YHD.User.init

这种划分方式有一个很明显的优点:你改购物车逻辑时,不会误碰商品渲染的代码。实际开发时我也习惯按业务域拆文件,比按技术类型拆(把所有的 utils 堆一个文件)后期维护省心得多。

3. 核心功能拆解:商品列表与购物车逻辑实战

想真正把这份代码吃透,要盯住两条线:一条是「数据怎么变成页面上的元素」,另一条是「用户操作怎么反向改数据」。商品列表和购物车恰好把这两条线完整走了一遍,代码量不大但五脏俱全。

3.1 商品列表渲染:从静态数据到 DOM 的链路

商品列表的渲染逻辑集中在 goods.js。它先定义了一个数组,每个商品对象包含id、name、price、img、category这些字段,然后通过循环拼接字符串的方式生成 HTML,一次性写入容器节点。

// goods.js var productList = [ { id: 1001, name: '伊利纯牛奶 250ml*12盒', price: 45.9, category: '食品', stock: 200 }, { id: 1002, name: '维达抽纸 3层100抽*24包', price: 49.9, category: '个护', stock: 150 }, { id: 1003, name: '农夫山泉 550ml*24瓶', price: 36.8, category: '食品', stock: 80 } ]; function renderGoods(filterCategory) { var container = document.getElementById('goods-list'); var html = ''; var list = productList; if (filterCategory) { list = productList.filter(function(item) { return item.category === filterCategory; }); } for (var i = 0; i < list.length; i++) { html += '<div class="goods-item">// cart.js var cartData = JSON.parse(localStorage.getItem('yhd_cart')) || {}; function addToCart(goodsId) { if (cartData[goodsId]) { cartData[goodsId].count++; } else { var product = YHD.Goods.findById(goodsId); cartData[goodsId] = { id: product.id, name: product.name, price: product.price, count: 1 }; } saveCart(); renderCart(); } function changeCount(goodsId, delta) { var item = cartData[goodsId]; if (!item) return; item.count += delta; if (item.count <= 0) { delete cartData[goodsId]; } saveCart(); renderCart(); } function saveCart() { localStorage.setItem('yhd_cart', JSON.stringify(cartData)); } function updateTotal() { var total = 0; for (var key in cartData) { total += cartData[key].price * cartData[key].count; } return Math.round(total * 100) / 100; }

这段代码里最需要理解的是cartData这个对象的数据结构。它以商品id作为 key,对应的 value 是{ id, name, price, count }。这种结构的好处是查找和更新都走对象键名,时间复杂度是 O(1),不用每次遍历整个数组。

addToCart里有两个细节容易忽略:当购物车中已有该商品时只做count++,不重复插入记录;当商品数量减到 0 时直接delete该键,保持数据干净。saveCart把整个对象序列化后写入 localStorage,这是整套代码的持久化核心,刷新页面后购物车数据不会丢。

我在实际项目里看到不少新人会把购物车写成「数组 push」方式,结果查重时被迫用indexOf遍历,性能和可读性都差。这份代码直接给你看到了一种更合理的方案,值得记下来。

4. 部署运行与联调:让本地代码真正跑起来

代码不是拿来看的,是拿来跑的。这一章我按自己实际操作的顺序,把从解压到页面能点、能加购、能结算的流程完整走一遍,包括本地服务怎么起、接口怎么对、mock 数据什么时候该换。

4.1 本地环境依赖与启动步骤

这套代码没有任何第三方依赖,不需要 npm install,不需要 package.json。它只需要一个静态文件服务把目录托管起来。我用的是 VS Code 的 Live Server 扩展,也可以用 Python 自带的 HTTP 服务:

# 进入项目根目录 cd yihaodian # 用 Python 启动静态文件服务(默认监听 8000 端口) python -m http.server 8000

启动后浏览器访问http://localhost:8000,看到商品列表页面就说明一切正常。注意一个习惯问题:直接从文件系统双击 index.html 也能打开,但浏览器对 file:// 协议下的某些资源加载有限制,而且 localStorage 在某些浏览器里对 file:// 页面是不稳定的,强烈建议走 HTTP 方式。

启动过程中如果页面空白,按 F12 打开开发者工具,切到 Console 面板。最常见的报错是资源加载路径不对,比如看到Failed to load resource: net::ERR_FILE_NOT_FOUND,这说明 css 或 js 的路径没有对上目录结构。

4.2 接口对接与 mock 数据切换

这套代码本身没有接后端接口,商品数据直接写在 goods.js 里。真实项目里数据必然来自后端接口,所以联调的时候要做两件事:把静态数组替换成 fetch 请求,同时保留一份 mock 数据兜底。

// 替换 goods.js 里 renderGoods 的数据来源 function loadGoods() { var mode = window.LOCAL_MOCK ? 'mock' : 'api'; if (mode === 'mock') { renderGoods(productList); return; } fetch('/api/goods') .then(function(res) { return res.json(); }) .then(function(data) { renderGoods(data); }) .catch(function() { // 接口挂了,退回本地数据 renderGoods(productList); }); }

这里的window.LOCAL_MOCK就相当于一个总开关:本地开发时在 HTML 里定义它并置为 true,就走 mock;联调时删掉这行定义,请求就会打到真实接口。catch里退回本地数据是兜底策略,避免接口异常导致页面完全白屏。

我建议你在联调时用一个浏览器插件来模拟接口响应,比如在 Network 面板里右键某个请求选择 Mock 响应,不改代码就能验证前端对各种返回值的处理。这样比你频繁改 JS 里的开关快得多。

5. 避坑与排查:这套代码最常见的五个问题

老代码结构简单,但踩坑一点都不少。我把实际跑这套代码时容易踩到的五个坑按「现象→原因→解决」的方式记录下来,你照着排查能省很多时间。

5.1 中文乱码

  • 现象:页面上的中文全部变成「锟斤拷」一类的乱码。
  • 原因:HTML 和 JS 文件的编码不一致。老项目大多采用 GBK 编码,但如果文件被某些编辑器默认保存为 UTF-8,浏览器按 GBK 解析就会出现乱码。
  • 解决:在 index.html 的 head 里保留<meta charset="gbk">,同时确认 js 和 css 文件都通过编辑器右下角编码菜单转成 GBK。如果不想继续用 GBK,最简单的办法是统一全部文件为 UTF-8,并把 charset 改成utf-8。

5.2 商品列表正常但加购按钮没反应

  • 现象:列表渲染出来了,但点击「加入购物车」没有任何反馈。
  • 原因:事件绑定在初始 DOM 上,但商品列表是通过innerHTML动态渲染的,事件监听器没有绑定到动态生成的按钮上。
  • 解决:把事件绑定向父级容器委托。常见做法是给 goods-list 容器绑 click,用event.target.closest('.js-add-cart')判断点击落在哪个按钮上。这份代码里如果只绑了静态节点,就会出现这个问题。

5.3 刷新页面后购物车数据丢失

  • 现象:加购成功,但一刷新,购物车清空了。
  • 原因:浏览器在 file:// 协议下对 localStorage 支持不稳定,或者代码里根本没有调用saveCart()。
  • 解决:确认访问方式是 http:// 而不是 file://;然后在 cart.js 里查一下addToCart最后是否执行了saveCart(),每次修改cartData后必须持久化一次。

5.4 价格计算出现 0.30000000000000004

  • 现象:购物车里两个商品价格相加,结果尾部冒出长串小数。
  • 原因:JavaScript 浮点数直接用*和+计算时存在精度损耗,比如 0.1 + 0.2 的结果不是 0.3。
  • 解决:先乘 100 转整数计算,再除 100 回显。代码里的updateTotal()用了Math.round(total * 100) / 100,这就是标准的处理方式。你要是自己新增了折扣逻辑,记住同样的原则。

5.5 后端字段名对不上,页面显示 undefined

  • 现象:接口联调后商品名显示undefined,价格不显示。
  • 原因:后端返回的字段是goods_name,而前端模板里用的是name,字段名不一致。
  • 解决:在 fetch 的.then里做一层数据映射,把后端字段名统一改成前端需要的结构,不要在前端模板里到处改字段名。比如{ name: raw.goods_name, price: raw.goods_price }。

6. 把这份代码改造成自己的项目:三个切实可行的做法

看完这份 1号店代码,如果只是会跑,价值只发挥了一半。真正有用的是把它拆开、改造、吸收成你的东西。我分享三个我实际用过的改造方向。

第一个做法是模块化重写。把 goods.js、cart.js 里的方法按功能拆出来,用 ES Module 的export/import替代原来的全局命名空间。这样每个文件职责更清晰,测试也好写。你可以用 Vite 起一个最小工程,把现有代码迁进去,不动业务逻辑,仅改引入方式。

第二个做法是把 DOM 操作封装成工具函数。老代码里到处是document.getElementById和element.innerHTML =,你可以借鉴现代框架的思路,封装一个el()方法和一个render()方法,让数据和视图的映射关系更直观。做好这一步,后续你要改成 Vue 或 React 会轻松很多。

第三个做法是把商品数据从静态数组改为接口驱动。我上面给的LOCAL_MOCK开关模式就是最实用的过渡方案——开发用本地数据,部署切接口,中间留一个兜底。这套模式放任何项目里都能用。

最后说一个我的血泪习惯:从那以后,我每次拿到别人的代码包,第一件事永远是开 Console 看有没有红色报错,然后完整走一遍「列表→加购→改数量→刷新→结算」的核心链路,确认数据在每一步都稳定。顺序对了,排错就快了。这套代码的完整逻辑和踩坑点都在上面了,希望你少走我走过的弯路,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询