实战:购物车功能
一、两个实用的数组函数
1.1 Array.every()
every()
方法传入回调函数,这个回调函数有三个参数,分别是:
- item:用于测试的当前值;
- index:当前测试值的索引,这是一个可选参数;
- every:用于测试的数组,这也是一个可选参数。
该方法的返回值是一个布尔值,如果回调函数的每一次的测试值返回的结果都是true,那么就返回 true ,否则返回 false。
简单理解即:只要有个值不满足条件,就它的返回值就是false了,这个跟逻辑判断的“逻辑与”非常像。
1.2 Array.some()
some()
方法传入回调函数,这个回调函数有三个参数,分别是:
- item: 正在被处理的数组中的元素;
- index: 正在被处理的数组中的元素的索引值,这是一个可选参数;
- some:
some
被调用的数组,这也是一个可选参数。
该方法的返回值是一个布尔值,如果数组中的有一个元素满足回调函数里的条件,那么结果就返回true,如果都不满足才返回 false。
简单理解即:只要有一个值满足条件,它的返回值就是true,这个跟逻辑判断的“逻辑或”非常像。
二、实战项目:购物车
2.1 功能和效果图
2.1.1 废话不多说,首先上个效果图,如下:
2.1.2 功能介绍:
点击全选按钮,每一项商品的复选框处于被勾选的状态,同时计算出商品数量和商品总价;
点击数量切换的按钮,能自动计算出修改数量之后的商品数量和价格;
商品的总计数量和总价格应该只计算被勾选的商品的数量和金额。
功能介绍完毕,下面开始介绍我写这个购物车的步骤。
2.2 购物车页面结构
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.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;
}
以上就是一个简单的购物车页面的HTML和CSS样式代码,效果图:
2.3 完成相关JS代码
首先,我们先完成商品的全选与全不选的功能。由于我们需要操作商品的全选与单选,肯定是需要拿到全选复选框元素和商品前面的复选框元素,代码如下:
// 1. 获取全选复选框,所有的商品都有一个独立的复选框
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]');
// 由于拿到的表单里的数据都是string类型的,所以需要先将其转换成int类型,因此需要使用`map()`方法操作一下
以上单价(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 ]
这时已经计算出来了每个商品的总金额,那么我们将其渲染到页面中。
// 获取单个商品总金额的元素数组
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]));
// 声明一个用于存储是商品数量的数组,该数组的作用是用于与对应的商品的状态值的数组进行相乘,得到实际的被选中的商品的数组。
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');
console.log(priceLists);
const priceArr = Array.from(priceLists).map(item => parseInt(item.textContent));
// 获取数量组成的数组
const numberLists = document.querySelectorAll('body input[type=number]');
console.log(numberLists);
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();
});