趁着过年放假在家复习了之前学的JS知识,用原生撸了一个购物车模块,下面我来整理一下我的思路分享给大家。
一、功能和效果图
1.1 废话不多说,首先上个效果图,如下:

购物车功能效果图
1.2 功能介绍:
- 点击全选按钮,每一项商品的复选框处于被勾选的状态,同时计算出商品数量和商品总价;
- 点击数量切换的按钮,能自动计算出修改数量之后的商品数量和价格;
- 商品的总计数量和总价格应该只计算被勾选的商品的数量和金额。
功能介绍完毕,下面开始介绍我写这个购物车的步骤。
二、购物车的页面结构
2.1 HTML代码
<table>
<caption>
购物车
</caption>
<thead>
<tr>
<!-- 全选复选框 -->
<th>
<input type="checkbox" name="checkAll" id="check-all" checked /><label for="check-all">全选</label>
</th>
<th>图片</th>
<th>品名</th>
<th>单位</th>
<th>单价/元</th>
<th>数量</th>
<th>金额/元</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1020" checked />
</td>
<td>
<a href=""><img src="images/p1.jpg" alt="" /></a>
</td>
<td>iPhone 11</td>
<td>台</td>
<td class="price">4799</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1020" checked />
</td>
<td>
<a href=""><img src="images/p2.jpg" alt="" /></a>
</td>
<td>小米pro 11</td>
<td>部</td>
<td class="price">3999</td>
<td><input type="number" min="1" value="2" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1030" checked />
</td>
<td>
<a href=""><img src="images/p3.jpg" alt="" /></a>
</td>
<td>MacBook Pro</td>
<td>台</td>
<td class="price">18999</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1040" checked />
</td>
<td>
<a href=""><img src="images/p4.jpg" alt="" /></a>
</td>
<td>小米75电视</td>
<td>台</td>
<td class="price">5999</td>
<td><input type="number" min="1" value="2" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1050" checked />
</td>
<td>
<a href=""><img src="images/p5.jpg" alt="" /></a>
</td>
<td>Canon 90D单反</td>
<td>台</td>
<td class="price">9699</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
</tbody>
<tfoot>
<tr style="font-weight: bolder; font-size: 1.2em">
<td colspan="5">总计:</td>
<td id="sum">xxxx</td>
<td id="total-amount">xxxx</td>
</tr>
</tfoot>
</table>
2.2 CSS代码
table {
border-collapse: collapse;
width: 90%;
text-align: center;
margin: auto;
}
table caption {
margin-bottom: 15px;
font-size: 1.5rem;
}
table th, table td {
border-bottom: 1px solid #ccc;
padding: 5px;
font-weight: normal;
}
table thead tr:first-of-type {
background-color: #e6e6e6;
height: 3em;
}
table input[type="checkbox"] {
width: 1.5em;
height: 1.5em;
}
table tbody tr {
border-bottom: 1px solid #ccc;
}
table tbody tr:hover {
background-color: #f6f6f6;
cursor: pointer;
}
tbody img {
width: 3em;
}
tbody input[type="number"] {
width: 3em;
}
button {
width: 150px;
height: 30px;
outline: none;
border: none;
background-color: teal;
color: white;
letter-spacing: 5px;
}
button:hover {
opacity: 0.7;
cursor: pointer;
}
2.3 效果图

购物车效果图
以上就是一个简单的购物车页面的HTML和CSS样式代码。
三、完成相关JS代码
首先,我们先完成商品的全选与取消全选的功能,所以肯定是需要拿到全选复选框元素和商品前面的复选框元素,代码如下:
// 获取全选复选框,所有的商品都有一个独立的复选框
const checkAll = document.querySelector('#check-all');
const checkItems = document.getElementsByName('item');
拿到全选和每个商品的复选框元素之后,给全选框添加一个change事件,监听它的checked值的变化。此时全选框的checked值可以通过事件监听回调函数中的ev参数下的ev.target.checked拿到。
checkALl.onchange = ev => {
// 如果全选框处于选中状态,ev.target.checked的值就为true,反之,为false。
console.log(ev.target.checked);
};
如果想让全选框的的状态和每个商品前的复选框状态保持一致,那么就使他们的checked值一致即可。因此,我们可以在全选复选框的change事件中遍历每个商品的复选框元素。
checkALl.onchange = ev => {
// 如果全选框处于选中状态,ev.target.checked的值就为true,反之,为false。
console.log(ev.target.checked);
checkItems.forEach(item => item.checked = ev.target.checked);
};
这样点击全选框的时候,就可以实现全部选中,和取消全选的功能了。效果如图:

全选与取消全选
全选和取消全选的功能完成之后,下面开始完善逐个勾选商品,直至勾选全部商品,让全选按钮自动变成被选中的状态。
要完成这个功能,我们可以通过对每个商品的复选框添加一个change事件来监听checked的变化。因此需要通过forEach()方法对遍历每一个商品。
checkItems.forEach(item => item.onchange = ev => {
// 在这里处理每一项的checked值
});
此时,我们可以这样考虑:当每个商品的复选框都被勾选,即:所有商品复选框的checked的值全部为true时,全选复选框才会显示被勾选的状态,也就是全选复选框的checked的值也要为true。
由于checkAll的状态依赖于每一项商品的checked值,那么可以利用一个数组函数:Array.every()遍历每一项商品,当所有商品的checked值都为true时,every()方法的返回值就是一个true,然后再赋值给checkAll即可。注意:由于我们拿到的checkItems是一个NodeList数组,需要先将其转换成数组后再进行操作。
checkItems.forEach(item => item.onchange = ev => {
checkAll.checked = Array.from(checkItems).every(checkItem => checkItem.checked);
});

点击选中每个商品
至此,全选和单选功能全部完成了。下面开始写自动计算金额的和总数的功能。
购物车的数量和金额不仅包含每一项商品的数量和每一项商品的总金额,还包含了计算选中的商品总数,以及所有选中的商品的总金额。
下面首先完成单个商品的总金额计算,总金额 = 单价 * 数量,根据这个公式,我们首先拿到商品的单价和数量元素。
// 获取单价组成的数组
const priceLists = document.querySelectorAll('.price');
// 获取数量组成的数组
const numberLists = document.querySelectorAll('body input[type=number]');
以上单价(priceLists)和数量(numberLists)都是NodeList类型的,需要先将它们转换成数组,由于表单中获取的内容都是string类型,而参与计算的需要的是整型,所以这里需要进行一下转换,使用parseInt()方法即可。
// 获取商品单价组成的数组
const priceLists = document.querySelectorAll('.price');
const priceArr = Array.from(priceLists).map(item => parseInt(item.textContent)); // [ 4799, 3999, 18999, 5999, 9699 ]
// 获取商品数量组成的数组
const numberLists = document.querySelectorAll('body input[type=number]');
const numbersArr = Array.from(numberLists).map(item => parseInt(item.value)); // 默认值:[ 1, 1, 1, 1, 1 ]
注意:商品价格和商品数量在取值时有些不同。商品的单价是普通元素直接使用textContent即可拿到它内部的值,而数量这个用的是表单控件,所以需要使用value才可以拿到值。 我刚开始写这个功能的时候懵逼了半天,此处一定要注意。
拿到商品的单价和数量之后就可以按照上面的公式进行计算了,由于商品的价格和商品的数量都是一个数组,并且价格和数量在数组中都是一一对应的关系,因此可以使用JS数组的reduce()方法进行遍历。
let amountArr = [priceArr, numbersArr].reduce((prev, curr) => {
return prev.map((item, index) => {
return item * curr[index];
});
});
总感觉上述写法有点怪怪的,是不是可以进行简化呢?根据箭头函数的特征,当只有一条返回语句的时候可以省略掉return关键字和大括号,因此上述方法可以简写成下面这样:
let amountArr = [priceArr, numbersArr].reduce((prev, curr) => prev.map((item, index) => item * curr[index]));
console.log(amountArr); // [ 4799, 3999, 18999, 5999, 9699 ]
(PS:上面的方法我一开始也没有发现可以简写,我是把代码发给我朋友看了之后,朋友给我点醒了。还是才疏学浅呀。)
这时已经计算出来了每个商品的总金额,那么我们将其渲染到页面中。
// 获取单个商品总金额的元素数组
const amountDOM = document.querySelectorAll('.amount');
amountDOM.forEach((item, index) => item.textContent = amountArr[index]);

计算每个商品的金额并渲染到页面中
单个商品的总金额渲染到页面之后,下面就开始计算商品的总数,和总金额了。根据某东、某宝的购物车功能,我们可以发现,总计那里统计的商品总数是一般是我们勾选上的商品总数,总金额也是一样的,那么我们就需要根据商品的状态来进行计算了。
首先声明一个数组,用于存储被选中的商品的状态,如果被选中,值为1,未被选中,则为0。
let isChecked = [];
checkItems.forEach(item => isChecked.push(item.checked === true ? 1 : 0));
// 打印出商品状态值
console.log(isChecked);
- 效果如图:

打印商品状态值
商品的状态已经记录好了,那么现在就需要统计选中的商品对应的数量了。
// 声明一个用于存储商品数量的数组,该数组的作用是用于与对应的商品的状态值的数组进行相乘,得到实际的被选中的商品的数组。
let checkedNumbers = [];
numbersArr.forEach((item, index) => checkedNumbers.push(item * isChecked[index]));
// 打印被选中的商品的数量
console.log(checkedNumbers);

打印出选中的商品的数量数组
计算出被选中的商品数量的总数并渲染到页面中:
let checkedSum = checkedNumbers.reduce((prev, curr) => prev + curr);
// 将获取的数量结果渲染到页面中
document.querySelector('#sum').textContent = checkedSum;
效果如上图已经出来了。
下面开始计算被选中的商品的总金额,该总金额等于上面所有被选中的商品的总金额之和。计算出结果之后渲染到页面中。
// 声明一个数组用于存储每一个被选中的商品的总金额
let checkedPrice = [];
checkedNumbers.forEach((item, index) => checkedPrice.push(item * priceArr[index]));
// 打印被选中的每个被选中的商品总金额
console.log(checkedPrice);
// 计算被选中的商品总金额
let totalAmount = checkedPrice.reduce((prev, curr) => prev + curr);
// 将选中的商品总金额渲染到页面中
document.querySelector('#total-amount').textContent = totalAmount;
- 效果图:

将总金额渲染到页面
至此,关于计算单个商品的总金额以及被选中商品的数量和总金额的功能已经全部完成了,但是我们还需要实现在页面加载以及更改某个商品数量时自动计算的功能。那么就需要将上述的计算功能封装成一个函数,以便后面每一次执行计算时使用。
- 封装后的代码如下:
function autoCalculate() {
// 获取单价组成的数组
const priceLists = document.querySelectorAll('.price');
const priceArr = Array.from(priceLists).map(item => parseInt(item.textContent));
// 获取数量组成的数组
const numberLists = document.querySelectorAll('body input[type=number]');
const numbersArr = Array.from(numberLists).map(item => parseInt(item.value));
console.log(priceArr, numbersArr);
// 由于拿到的表单里的数据都是string类型的,所以需要先将其转换成int类型,因此需要使用`map()`方法操作一下
let amountArr = [priceArr, numbersArr].reduce((prev, curr) => prev.map((item, index) => item * curr[index]));
console.log(amountArr);
const amountDOM = document.querySelectorAll('.amount');
amountDOM.forEach((item, index) => item.textContent = amountArr[index]);
// 首先声明一个数组,用于存储被选中的商品的状态,如果被选中,值为1,未被选中,则为0
let isChecked = [];
checkItems.forEach(item => isChecked.push(item.checked === true ? 1 : 0));
console.log(isChecked);
// 声明一个用于存储是商品数量的数组,该数组的作用是:如果商品处于被选中的状态,那么就存储它真实的数量值,
// 如果没有被选中,那么数量就是0
let checkedNumbers = [];
numbersArr.forEach((item, index) => checkedNumbers.push(item * isChecked[index]));
console.log(checkedNumbers);
// 此时,被选中的商品的总数为:
let checkedSum = checkedNumbers.reduce((prev, curr) => prev + curr);
console.log(checkedSum);
// 将获取的数量结果渲染到页面中
document.querySelector('#sum').textContent = checkedSum;
// 下面开始计算被选中的商品的总金额,该总金额等于上面所有被选中的商品的总金额之和。
// 声明一个数组用于存储每一个被选中的商品的总金额
let checkedPrice = [];
checkedNumbers.forEach((item, index) => checkedPrice.push(item * priceArr[index]));
console.log(checkedPrice);
// 计算被选中的商品总金额
let totalAmount = checkedPrice.reduce((prev, curr) => prev + curr);
// 将选中的商品总金额渲染到页面中
document.querySelector('#total-amount').textContent = totalAmount;
}
将代码封装后我们会发现,单个商品的总金额,商品总数以及总金额的值都没了,如下图:

封装代码后的效果
这是因为,代码在第一次加载的时候并没有执行封装后的函数,因此需要加一行代码:
// 页面第一次加载的时候自动执行一次。
window.onload = autoCalculate;
这样页面中的数据在第一次加载的时候就全部都正常了。
下面完成最后一个功能:调整商品的数量,会自动计算总数和金额。该功能还是通过change事件监听某个表单数据的变化来完成。效果图下图:

- 代码实现:
// 监听某个控件的事件,首先需要拿到控件元素。
const numInput = document.querySelectorAll('body input[type=number]');
// 上面都用了onchange来监听,这里换个方法使用addEventListener。
numInput.forEach(item => item.addEventListener('change', autoCalculate));
但是我们会发现这里有个小bug,就是如果勾选没有选中的商品,并不会自动计算商品数量和总价,原因很简单,我们在监听单个商品选中和全选的时候根本就没有执行自动计算函数,只需要在二者的事件监听中加上自动计算的函数即可。
checkAll.onchange = ev => {
checkItems.forEach(item => item.checked = ev.target.checked);
// 解决勾选全选框不会自动计算的bug
autoCalculate();
};
checkItems.forEach(item => item.onchange = ev => {
checkAll.checked = Array.from(checkItems).every(checkItem => checkItem.checked);
// 解决勾选全选框不会自动计算的bug
autoCalculate();
});
写到这里,我们购物车的所有功能都已经完成了。购物车这个模块看似不难,其实这里面的坑也是不少的,例如:
- 在操作获取的元素节点时,我们有时候需要将其转换成一个数组才可以使用数组函数进行操作,因为我们通过document.querySelector获取的元素并不是一个真正的数组,而是一个类数组(NodeList);
- 案例中使用了多个数组函数,Array.from()、Array.reduce()、Array.every()等等,由此可见,熟练掌握JS常用的数组也是非常重要的;
- 掌握事件监听:addEventListener;
- 箭头函数的简写方法;
- 表单事件的监听,只能通过onchange方法,千万不要使用onclick。
以上就是我个人在写这个购物车功能的全部新的,由于本人也是新手,可能还有其他更简介方便的写法,如果有问题,请各位大佬批评指正,不胜感激。
如果有刚开始学习JS的同学,想要源码的各位亲,可以关注并私信回复“购物车”即可。