博客列表 >购物车三种实现(原生JS、jquery和vuejs)

购物车三种实现(原生JS、jquery和vuejs)

吾逍遥
吾逍遥原创
2020年11月14日 21:29:533203浏览

一、购物车功能描述及实现思路

1、购物车功能描述

  1. 全选复选框勾选时,所有商品单项全选,全选不勾选时,所有商品单项也不勾选
  2. 商品单项有一项不勾选时,全选复选框自动不勾选,当所有商品单项勾选时,全选复选框则自动勾选
  3. 商品单项数量影响商品单项总价
  4. 商品单项数量影响商品总数量
  5. 商品单项数量影响商品总价格
  6. 商品单项是否勾选影响商品总数量
  7. 商品单项是否勾选影响商品总价格

2、实现思路

虽然后面采用了原生JS、jquery和vuejs实现,但基本思路都是一样,尤其是核心问题的解决问题的思路。

第1项 其实就是将全选复选框的状态赋值给每一个单项选择即可,这样全选和单选就同步了,无论是勾选还是不选。

第2项 我的思路和老师一样,不过实现方式不一样,老师是使用计数器,通过计数器是否等于总数来决定全选是否勾选。我的是直接 统计勾选的个数,和总数比较 ,相等则自动勾选全选,不等则不勾选。

第3、4、5项 为所有商品数量输入框添加事件,在事件函数中可对商品单项总价、商品总数量和商品总价格计算。 难点在于获取是第几个商品数量输入框,这样可改变对应商品单项总价 。老师所有都是采用数组遍历,我认为不合理,因为商品单项数量改变时其单项总价只要计算它就可以,所有都计算一遍,目前只有几个商品没关系,若是几百个就会浪费不必要的时间了,降低了效率。这时就需要知道是哪个商品数量改变了,这就需要事件监听器添加索引就可以,可惜直接添加是无效的。下面是正确添加方式,大家可以试着理解为什么这样就可以了??jquery也有类似实现,至于vuejs则是双向绑定就简单多了。

  1. // 监听事件传参不能直接传参数,而是通过function((ev){函数名(ev,自定义参数)}) ******
  2. for (let [index, item] of checkItemBtns.entries()) {
  3. item.addEventListener( 'click', function (ev) { checkItem(ev, index); }, false );
  4. }

第5、6项 商品单项是否勾选将影响商品总数量和总价格,至于数量和商品单项总价则不受影响。我的解决思路就是设置开关,对每个商品设置开关,若勾选则为1时计算它,不勾选则为0不计算它。这里说明下原生JS和jquery中目前唯一问题是开关对单项商品总价有影响,当然可以加两行代码就解决了。至于vuejs则全部实现了所有功能。

二、原生JS实现购物车

这里原生JS实现购物车基本就是按上面思路来完成的,具体实现可看下面源码。这里唯一强调一点就是商品单项数量改变是只会改变它的单项总价、总数量和总价格这三个位置。实现就是用上面原理中说的给事件监听器的回调函数传递参数,也是事件监听器的进阶用法了,大家可以参考下。

至于我勾选单项影响单项总价、总数量和总价格三个位置,如果你认为不合理可以简单修改就可以。下面jq也是如此效果,vuejs则只影响总数量和总价格了。

  1. <style>
  2. * {
  3. margin: 0;
  4. padding: 0;
  5. box-sizing: border-box;
  6. }
  7. table {
  8. border-collapse: collapse;
  9. width: 90vw;
  10. min-width: 680px;
  11. text-align: center;
  12. margin: auto;
  13. }
  14. table caption {
  15. font-size: 1.5em;
  16. margin-bottom: 15px;
  17. }
  18. table th,
  19. table td {
  20. border: 1px solid;
  21. padding: 5px;
  22. }
  23. table thead tr {
  24. background-color: lightblue;
  25. }
  26. table tbody tr:nth-child(even) {
  27. background-color: lightcyan;
  28. }
  29. table input {
  30. text-align: center;
  31. line-height: 2em;
  32. }
  33. table input[type='checkbox'] {
  34. width: 1.5em;
  35. height: 1.5em;
  36. }
  37. .btns {
  38. width: 90%;
  39. margin: 1em auto;
  40. display: flex;
  41. justify-content: flex-end;
  42. }
  43. button {
  44. width: 8em;
  45. height: 2em;
  46. outline: none;
  47. border: none;
  48. background-color: seagreen;
  49. color: white;
  50. letter-spacing: 1em;
  51. }
  52. </style>
  53. <table>
  54. <caption>购物车</caption>
  55. <thead>
  56. <tr>
  57. <th>
  58. <input type="checkbox" name="checkAll" id="check-all" checked />
  59. <label for="check-all">全选</label>
  60. </th>
  61. <th>ID</th>
  62. <th>品名</th>
  63. <th>单位</th>
  64. <th>单价/元</th>
  65. <th>数量</th>
  66. <th>金额/元</th>
  67. </tr>
  68. </thead>
  69. <tbody>
  70. <tr>
  71. <td>
  72. <input type="checkbox" name="itemId" value="SN-1010" checked />
  73. </td>
  74. <td>SN-1010</td>
  75. <td>MacBook Pro电脑</td>
  76. <td></td>
  77. <td>18999</td>
  78. <td>
  79. <input type="number" name="counter" value="1" min="1" step="1" />
  80. </td>
  81. <td></td>
  82. </tr>
  83. <tr>
  84. <td>
  85. <input type="checkbox" name="itemId" value="SN-1020" checked />
  86. </td>
  87. <td>SN-1020</td>
  88. <td>iPhone手机</td>
  89. <td></td>
  90. <td>4999</td>
  91. <td>
  92. <input type="number" name="counter" value="2" min="1" step="1" />
  93. </td>
  94. <td></td>
  95. </tr>
  96. <tr>
  97. <td>
  98. <input type="checkbox" name="itemId" value="SN-1030" checked />
  99. </td>
  100. <td>SN-1030</td>
  101. <td>智能AI音箱</td>
  102. <td></td>
  103. <td>399</td>
  104. <td>
  105. <input type="number" name="counter" value="3" min="1" step="1" />
  106. </td>
  107. <td></td>
  108. </tr>
  109. <tr>
  110. <td>
  111. <input type="checkbox" name="itemId" value="SN-1040" checked />
  112. </td>
  113. <td>SN-1040</td>
  114. <td>SSD移动硬盘</td>
  115. <td></td>
  116. <td>888</td>
  117. <td>
  118. <input type="number" name="counter" value="4" min="1" step="1" />
  119. </td>
  120. <td></td>
  121. </tr>
  122. <tr>
  123. <td>
  124. <input type="checkbox" name="itemId" value="SN-1050" checked />
  125. </td>
  126. <td>SN-1050</td>
  127. <td>黄山毛峰</td>
  128. <td></td>
  129. <td>999</td>
  130. <td>
  131. <input type="number" name="counter" value="5" min="1" step="1" />
  132. </td>
  133. <td></td>
  134. </tr>
  135. </tbody>
  136. <tfoot>
  137. <tr>
  138. <td colspan="5">总计:</td>
  139. <td id="total-num"></td>
  140. <td id="total-amount"></td>
  141. </tr>
  142. </tfoot>
  143. </table>
  144. <div class="btns">
  145. <button>结算</button>
  146. </div>
  147. <script>
  148. // 为了同步选择项,建立数组,其值为1为选中,0为未选中,(实现5,6,通过1或0判断单项是否有效来控制)
  149. let checkArr = [];
  150. // 一、全选和自选(实现1,2)
  151. // 获取全选按钮
  152. const checkAllBtn = document.querySelector('#check-all');
  153. // 获取单项选择按钮
  154. const checkItemBtns = document.querySelectorAll('input[name="itemId"]');
  155. // 全选按钮添加点击事件,将它的状态赋值给所有单项选择按钮
  156. checkAllBtn.addEventListener('click', checkAll, false);
  157. function checkAll(ev) {
  158. for (let [index, item] of checkItemBtns.entries()) {
  159. checkArr[index] = ev.target.checked ? 1 : 0;
  160. item.checked = ev.target.checked;
  161. }
  162. autoCount();
  163. }
  164. // 所有单项选择按钮添加点击事件,老师用change,这里用click事件感觉更为合理,当然效果目前是一样。
  165. // 监听事件传参不能直接传参数,而是通过function((ev){函数名(ev,自定义参数)}) ******
  166. for (let [index, item] of checkItemBtns.entries()) {
  167. // 初始化单项状态
  168. checkArr[index] = item.checked ? 1 : 0;
  169. item.addEventListener(
  170. 'click',
  171. function (ev) {
  172. checkItem(ev, index);
  173. },
  174. false
  175. );
  176. }
  177. function checkItem(ev, index) {
  178. checkArr[index] = checkItemBtns[index].checked ? 1 : 0;
  179. // 单项选择按钮点击时,就获取单项选择按钮选中的个数
  180. const selectItemBtns = document.querySelectorAll('input[name="itemId"]:checked');
  181. // 若选中个数等于总个数则全选,否则取消全选。老师是用计数器的方法,感觉没这种方法简洁明了。
  182. checkAllBtn.checked = selectItemBtns.length == checkItemBtns.length ? true : false;
  183. autoCount();
  184. }
  185. // 二、自动计算(实现3,4,5)
  186. // 获取单价元素组
  187. const unitPriceArr = document.querySelectorAll('tbody>tr>td:nth-child(5)');
  188. // console.log(unitPriceArr[0].innerHTML);
  189. // 获取数量输入框元素组
  190. const inputCounterArr = document.querySelectorAll('input[name="counter"]');
  191. // console.log(inputCounterArr[1].value);
  192. // 获取单项总价元素组
  193. const unitPriceTotalArr = document.querySelectorAll('tbody>tr>td:nth-child(7)');
  194. // console.log(unitPriceTotalArr[unitPriceTotalArr.length-1].innerHTML);
  195. // 为数量输入框添加变化事件
  196. for (let inputCounter of inputCounterArr) {
  197. inputCounter.addEventListener('change', autoCount, false);
  198. }
  199. window.addEventListener('load', autoCount, false);
  200. function autoCount() {
  201. console.log(checkArr);
  202. // 获取输入数量数组,parseInt转换为整数压入数组
  203. let inputArr = [];
  204. let unitTotal = 0;
  205. for (let [index, inputCounter] of inputCounterArr.entries()) {
  206. inputArr.push(parseInt(inputCounter.value) * checkArr[index]);
  207. }
  208. // 计算数量总和并赋值给统计的总数,数组reduce是求和的利器。
  209. document.querySelector('#total-num').innerHTML = inputArr.reduce((a, b) => a + b, 0);
  210. // 改变单项总价并累加到总价上
  211. for (let [index, unitPriceTotal] of unitPriceTotalArr.entries()) {
  212. unitTotal += inputArr[index] * unitPriceArr[index].innerHTML * checkArr[index];
  213. unitPriceTotal.innerHTML = inputArr[index] * unitPriceArr[index].innerHTML;
  214. }
  215. // 给总价赋值
  216. document.querySelector('#total-amount').innerHTML = unitTotal;
  217. }
  218. </script>

三、jquery实现购物车

jquery中页面和CSS都和原生一样,这里就不重复了。这里重点说下几个注意点:

  1. input的checked获取和设置问题 我们知道input的checked是布尔值,但attr()获取是字符串,这里是使用prop代替解决问题,记得也有问这个问题,查了网上资料也是难懂,还是拿出我们的利器:自己测试吗!
  1. <label for="c1">c1:</label>
  2. <input id="c1" name="checkbox" type="checkbox" checked="checked" />
  3. <label for="c2">c2:</label>
  4. <input id="c2" name="checkbox" type="checkbox" checked="true" />
  5. <label for="c3">c3:</label>
  6. <input id="c3" name="checkbox" type="checkbox" checked="" />
  7. <label for="c4">c4:</label>
  8. <input id="c4" name="checkbox" type="checkbox" checked />
  9. <label for="c5">c5:</label>
  10. <input id="c5" name="checkbox" type="checkbox" />
  11. <label for="c6">c6:</label>
  12. <input id="c6" name="checkbox" type="checkbox" checked="false" />
  13. <script>
  14. // attr
  15. console.log('c1 attr => ',$('#c1').attr('checked'));
  16. console.log('c2 attr => ',$('#c2').attr('checked'));
  17. console.log('c3 attr => ',$('#c3').attr('checked'));
  18. console.log('c4 attr => ',$('#c4').attr('checked'));
  19. console.log('c5 attr => ',$('#c5').attr('checked'));
  20. console.log('c6 attr => ',$('#c6').attr('checked'));
  21. // prop
  22. console.log('c1 prop => ',$('#c1').prop('checked'));
  23. console.log('c2 prop => ',$('#c2').prop('checked'));
  24. console.log('c3 prop => ',$('#c3').prop('checked'));
  25. console.log('c4 prop => ',$('#c4').prop('checked'));
  26. console.log('c5 prop => ',$('#c5').prop('checked'));
  27. console.log('c6 prop => ',$('#c6').prop('checked'));
  28. </script>

attr-prop

这里要说明下,上面是测试代码是改进于脚本之家网站一篇文章,但是文章作者的结论我不赞同,我增加了内置属性和自定义属性测试,得出了attr和prop的区别:

  • 元素 内置属性并且指明 时,attr和prop都是返回属性值。
  • 元素 内置属性未指明时,attr返回undefined,prop返回false。
  • 元素 内置属性的属性值只有true或false时,如checked、selected和disabled等,attr返回属性名字符串,指明属性名时prop返回true,未指明时返回false。
  • 元素 自定义属性如data-xxx等,attr正常获取属性值,而prop则无效,返回undefined。
  1. jQuery自带迭代(自带循环)如何获取第几个成员? 当然可以使用原生JS中for…of方法获取,这里说下jq本身就支持的简便方法 index ,它 返回数组中某成员的索引 。代码就是: let index=$('selector').index(this);

该解决的问题都进行了单独说明,下面就是实现了,基本是由原生JS转换过来的,除了上面两点,其它也没有什么了。

  1. let checkArr = [];
  2. $('input[name="itemId"]').each((index, item) => (checkArr[index] = $(item).prop('checked') ? 1 : 0));
  3. // 一、全选和自选(实现1、2)
  4. // 获取单值如checked
  5. $('#check-all').click(function () {
  6. $('input[name="itemId"]').prop('checked', $(this).prop('checked'));
  7. $('input[name="itemId"]').each((index, item) => (checkArr[index] = $(item).prop('checked') ? 1 : 0));
  8. autoCount();
  9. });
  10. $('input[name="itemId"]').click(function () {
  11. // jq中快速获取是第几个成员的方法
  12. let index = $('input[name="itemId"]').index(this);
  13. checkArr[index] = $(this).prop('checked') ? 1 : 0;
  14. $('#check-all').prop('checked', $('input[name="itemId"]:checked').length == $('input[name="itemId"]').length);
  15. autoCount();
  16. });
  17. // 二、自动计算(实现3,4,5)
  18. window.addEventListener('load', autoCount, false);
  19. // 获取单价元素组
  20. let unitPriceArr = [];
  21. $('tbody>tr>td:nth-child(5)').each((index, item) => (unitPriceArr[index] = item.innerHTML * 1));
  22. $('input[name="counter"]').each((index, item) => { $(item).change(function (ev) { autoCount(ev, index); }); });
  23. function autoCount(ev, index) {
  24. let goodNumArr = [];
  25. let totalAmount = 0;
  26. $('input[name="counter"]').each((index, item) => (goodNumArr[index] = $(item).val() * 1 * checkArr[index]));
  27. $('#total-num').html(goodNumArr.reduce((a, b) => a + b, 0));
  28. $('tbody>tr>td:nth-child(7)').each((index, item) => {
  29. totalAmount += unitPriceArr[index] * goodNumArr[index] * checkArr[index];
  30. $(item).html(unitPriceArr[index] * goodNumArr[index]);
  31. });
  32. $('#total-amount').html(totalAmount);
  33. }

四、vuejs实现购物车

本来是完成老师作业就完事了,后来又听了老师vuejs的课,尤其是老师用原生JS模拟实现和解释vuejs本质后,让我思路一下就开阔了,原生JS是这些一切的根基,jquery函数库、vuejs、react都是在其基础上进行了封装,更便于开发者开发,同时也降低了开发者对原生JS理解。在这些库或框架中都可混用原生JS,也可用原生JS实现,掌握了原生JS学习这些框架或库都是随手拈来。通过这几天原生JS学习,算是原生JS真正入门了,学习jquery和vuejs比以前只知道使用深刻多了,不再出现使用什么解决问题了,具体查手册就可以了,随着累积相信可以应付绝大部分问题。

vuejs在数据处理方面最强大就是双向绑定了,而购物车则也是这方面需求,我就试着改造成vuejs实现,通过测试终于完成了,下面进行简单说明

  1. 页面中数据双向绑定 数据双向绑定一定要使用v-model ,我开发过uniapp,使用最多的变量方式是:冒号,结果测试时只能随着vuejs中data数据而改变,它不能改变绑定该变量的值,后来还是使用v-model实现的,这里只是强调,双向绑定v-model指令不可少。

  2. 计算属性如何传参数 老师演示了计算属性并解释了本质,在本案例中计算单项商品总价格时,只想改变单项商品对应的总价格,如文章开头所说,需要知道索引,查了vuejs官方文档,知道了计算属性默认是get方法,还有set方法,但是不满足本题要求,后来突然想到不能传递参数是因为 计算属性一般情况下返回值是表达式,所以不能接受参数 ,那么 如果定义为返回值是函数呢?测试了下,成功 。一个非常有用的技巧呢!!!乍一看和上面监听事件中的回调函数传递参数实现类似,都是将返回值转成函数,从而接受参数。

  3. 本实现方法中全选和自选都是能完监听数据变化来完成,这样就存在一个问题,就是自选导致全选监听再一次执行全选监听,所有我们必须知道全选变化是手动还是自动,通过给全选添加自定义方法,设置开关就解决了。

  1. <table>
  2. <caption>购物车</caption>
  3. <thead>
  4. <tr>
  5. <th>
  6. <input type="checkbox" name="checkAll" id="check-all" v-model="checkAll" v-on:input="chekcAllStatus" />
  7. <label for="check-all">全选</label>
  8. </th>
  9. <th>ID</th>
  10. <th>品名</th>
  11. <th>单位</th>
  12. <th>单价/元</th>
  13. <th>数量</th>
  14. <th>金额/元</th>
  15. </tr>
  16. </thead>
  17. <tbody>
  18. <tr>
  19. <td>
  20. <input type="checkbox" name="itemId" value="SN-1010" v-model="checkArr[0]" />
  21. </td>
  22. <td>SN-1010</td>
  23. <td>MacBook Pro电脑</td>
  24. <td></td>
  25. <td>{{unitPriceArr[0]}}</td>
  26. <td>
  27. <input type="number" name="counter" v-model:value="inputArr[0]" min="1" step="1" />
  28. </td>
  29. <td>{{unitTotalPrice(0)}}</td>
  30. </tr>
  31. <tr>
  32. <td>
  33. <input type="checkbox" name="itemId" value="SN-1020" v-model="checkArr[1]" />
  34. </td>
  35. <td>SN-1020</td>
  36. <td>iPhone手机</td>
  37. <td></td>
  38. <td>{{unitPriceArr[1]}}</td>
  39. <td>
  40. <input type="number" name="counter" v-model:value="inputArr[1]" min="1" step="1" />
  41. </td>
  42. <td>{{unitTotalPrice(1)}}</td>
  43. </tr>
  44. <tr>
  45. <td>
  46. <input type="checkbox" name="itemId" value="SN-1030" v-model="checkArr[2]" />
  47. </td>
  48. <td>SN-1030</td>
  49. <td>智能AI音箱</td>
  50. <td></td>
  51. <td>{{unitPriceArr[2]}}</td>
  52. <td>
  53. <input type="number" name="counter" v-model:value="inputArr[2]" min="1" step="1" />
  54. </td>
  55. <td>{{unitTotalPrice(2)}}</td>
  56. </tr>
  57. <tr>
  58. <td>
  59. <input type="checkbox" name="itemId" value="SN-1040" v-model="checkArr[3]" />
  60. </td>
  61. <td>SN-1040</td>
  62. <td>SSD移动硬盘</td>
  63. <td></td>
  64. <td>{{unitPriceArr[3]}}</td>
  65. <td>
  66. <input type="number" name="counter" v-model:value="inputArr[3]" min="1" step="1" />
  67. </td>
  68. <td>{{unitTotalPrice(3)}}</td>
  69. </tr>
  70. <tr>
  71. <td>
  72. <input type="checkbox" name="itemId" value="SN-1050" v-model="checkArr[4]" />
  73. </td>
  74. <td>SN-1050</td>
  75. <td>黄山毛峰</td>
  76. <td></td>
  77. <td>{{unitPriceArr[4]}}</td>
  78. <td>
  79. <input type="number" name="counter" v-model:value="inputArr[4]" min="1" step="1" />
  80. </td>
  81. <td>{{unitTotalPrice(4)}}</td>
  82. </tr>
  83. </tbody>
  84. <tfoot>
  85. <tr>
  86. <td colspan="5">总计:</td>
  87. <td id="total-num">{{totalNum}}</td>
  88. <td id="total-amount">{{totalPrice}}</td>
  89. </tr>
  90. </tfoot>
  91. </table>
  92. <script>
  93. const vm = new Vue({
  94. el: 'table',
  95. data: {
  96. checkAll: true,
  97. checkArr: [true, true, true, true, true],
  98. // 开关,防止全选和单选冲突
  99. checkStatus: false,
  100. unitPriceArr: [18999, 4999, 399, 888, 999],
  101. inputArr: [3, 2, 3, 4, 5],
  102. },
  103. computed: {
  104. totalNum: function () {
  105. // return this.inputArr.reduce((a, b) => a * 1 + b * 1, 0);
  106. let total = 0;
  107. for (let [index, input] of this.inputArr.entries()) {
  108. total += input * 1 * (this.checkArr[index] ? 1 : 0);
  109. }
  110. return total;
  111. },
  112. unitTotalPrice: function () {
  113. return index => {
  114. return this.unitPriceArr[index] * 1 * this.inputArr[index];
  115. };
  116. },
  117. totalPrice: function () {
  118. let total = 0;
  119. for (let [index, input] of this.inputArr.entries()) {
  120. total += this.unitPriceArr[index] * 1 * input * (this.checkArr[index] ? 1 : 0);
  121. }
  122. return total;
  123. },
  124. },
  125. watch: {
  126. checkAll(newVal) {
  127. // 双向绑定数组无法直接赋值,需要转换为普通的,计算完后,使用vuejs改变即可
  128. if (this.checkStatus) {
  129. let checkArr = JSON.parse(JSON.stringify(this.checkArr));
  130. for (let index of checkArr.keys()) {
  131. checkArr[index] = newVal;
  132. }
  133. this.checkArr = checkArr;
  134. this.checkStatus = false;
  135. }
  136. },
  137. checkArr() {
  138. let checkArr = JSON.parse(JSON.stringify(this.checkArr));
  139. let checkOkArr = checkArr.filter(item => item == true);
  140. this.checkAll = checkArr.length == checkOkArr.length ? true : false;
  141. },
  142. },
  143. methods: {
  144. chekcAllStatus: function () {
  145. // 区分是用户选择全选,还是单项导致自动全选
  146. this.checkStatus = true;
  147. },
  148. },
  149. });
  150. </script>

Codepen演示 https://codepen.io/woxiaoyao81/pen/zYBeEoO

五、感受

原生JS、jquery函数库和vuejs框架基本学完了,前端也快基本告以段落了,朱老师深入浅出的授课让我对前端也完成了系统的梳理,相比报前最烦恼的就是前端,无论是CSS还是JS,都感觉乱,需要就百度来东拼本西凑,最后一团乱麻。现在对前端已经有了系统的认识,现在无论是语义化元素,还是CSS布局,或是JS的使用,都能快速找到解决思路,个别不清楚查下手册就可以了。我的理解都已经写在我的博文中了,可以说好多都比网上大部分文章介绍更系统更深入,相信你看了之后也会对前端有了系统的认识。

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议