JS 重构购物车案例 改进版(详细注释)

html:
<!DOCTYPE html> <html lang="cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="./style/shop.css"> <script src="./js/shop.js"></script> </head> <body> <ul id="shopBox" class="shopBox"></ul> <script> var shopCar var obj = [{ "id": 1001, "icon": "img/1.png", "name": "大白菜", "num": 1, "price": 10 }, { "id": 1002, "icon": "img/2.png", "name": "胡萝卜", "num": 1, "price": 230 }, { "id": 1003, "icon": "img/3.png", "name": "电视机", "num": 1, "price": 340 }, { "id": 1004, "icon": "img/4.png", "name": "啤酒", "num": 1, "price": 4023 }, { "id": 1005, "icon": "img/5.png", "name": "摩托车", "num": 1, "price": 5103 }, { "id": 1006, "icon": "img/6.png", "name": "路由器", "num": 1, "price": 602 }, { "id": 1007, "icon": "img/7.png", "name": "西红柿", "num": 1, "price": 70 }, { "id": 1008, "icon": "img/8.png", "name": "苹果", "num": 1, "price": 802 }, { "id": 1009, "icon": "img/9.png", "name": "书笔", "num": 1, "price": 9021 }, { "id": 1010, "icon": "img/10.png", "name": "手机", "num": 1, "price": 9899 }]; singleCar().init(obj, shopBox); function singleCar() { //单例模式,只产生一个实例 if (!shopCar) { shopCar = new ShopCar() } return shopCar } </script> </body> </html>
css
* {
margin: 0;
padding: 0;
}
.shopBox {
overflow: hidden;
width: 1400px;
margin: 50px auto;
}
.liItem {
float: left;
list-style: none;
padding: 10px;
width: 100px;
height: 120px;
text-align: center;
border: 1px solid lightcoral;
}
.liItem img {
width: 30px;
height: 30px;
}
.leftBtn,
.rightBtn {
width: 30px;
height: 30px;
background: white;
border: 1px solid black;
font-size: 25px;
line-height: 30px;
}
.text {
width: 50px;
height: 26px;
display: inline-block;
vertical-align: bottom;
text-align: center;
}
table {
font-size: 20px;
width: 1000px;
border: 1px solid lightcoral;
border-collapse: collapse;
margin: 50px auto;
}
.checkbox {
width: 30px;
height: 30px;
}
td {
border: 1px solid lightcoral;
text-align: center;
vertical-align: middle;
}
td button {
width: 100px;
height: 40px;
}
.numBox {
width: 100px;
height: 30px;
margin: auto;
position: relative;
}
.numBox>button {
width: 30px;
height: 42px;
background-color: white;
border: 1px solid #000000;
}
.numBox>input {
width: 40px;
height: 40px;
border: 1px solid #000000;
border-left: none;
border-right: none;
text-align: center;
}
js
function ShopCar() { } ShopCar.prototype = (function() { //闭包存放私有变量 return { shopList: null, //商品详情列表 shoppingList: null, //购物车中的商品 table: null, //购物车列表容器 ADD_ITEM_EVENT: "add_item_event", //添加单个商品自定义事件 REDUCE_ITEM_EVENT: "reduce_item_event", //减少单个商品自定义事件 DDELETE_ITEM_EVENT: "delete_item_event", //删除单个商品自定义事件 INPUT_ITEM_EVENT: "input_item_event", //修改商品数量自定义事件 CHECK_ITEM_EVENT: "check_item_event", //全选自定义事件 /* 初始化函数 1、获取存储中的商品列表数据 2、如果商品购物车列表在存储器中存在,那么就将这个数据获取赋值给购物车列表 如果不存在,创建一个空数组 3、侦听添加商品事件,侦听删除商品事件,侦听修改商品数量事件,侦听选中商品事件 4、循环商品列表,创建所有的商品标签 5、根据购物车列表创建表格 * */ init(shopList, parentEle) { if (localStorage.shoppingList) { //获取本地缓存中已加入购物车的数据 this.shoppingList = JSON.parse(localStorage.shoppingList); } else { this.shoppingList = []; } if (!this.shopList) { this.shopList = shopList this.createShopList(parentEle) } // 以下均为操作时的事件监听,采取自定义事件接收document的事件抛发,分别是:加入购物车(增加某一个商品),减少一个商品,删除某商品,输入商品数量,全选/取消全选 document.addEventListener(this.ADD_ITEM_EVENT, this.addItemEventHandler) document.addEventListener(this.REDUCE_ITEM_EVENT, this.reduceItemEventHandler) document.addEventListener(this.DDELETE_ITEM_EVENT, this.deleteItemEventHandler) document.addEventListener(this.INPUT_ITEM_EVENT, this.inputItemEventHandler) document.addEventListener(this.CHECK_ITEM_EVENT, this.checkItemEventHandler) }, createShopList(parentEle) { //创建商品详情列表 let data = this.shopList for (let i = 0; i < data.length; i++) { let li = document.createElement("li"); li.className = "liItem"; let img = new Image(); img.src = data[i].icon; li.appendChild(img); let title = document.createElement("div"); title.textContent = data[i].name; li.appendChild(title); let price = document.createElement("span"); price.textContent = data[i].price + "元"; li.appendChild(price); let addShops = document.createElement("p"); addShops.innerHTML = "加入购物车" li.appendChild(addShops) addShops.style.backgroundColor = "red"; addShops.style.height = "40px"; addShops.style.lineHeight = "40px"; addShops.style.borderRadius = "10px"; li.that = this //将全局this存至元素中,方便后续调用 li.data = data[i]; //与上句同理,方便后续调用 li.addEventListener("click", this.addItemEvent); parentEle.appendChild(li); } this.createTable() }, createTable() { //当数据发生变化时重新渲染购物车列表初始化购物车 if (this.table) { //初始化购物车 this.table.remove() this.table = null; } if (this.shoppingList.length === 0) return; this.table = document.createElement("table"); document.body.appendChild(this.table); let thr = document.createElement("tr"); this.table.appendChild(thr); for (let prop in this.shoppingList[0]) { //创建表头,如果属性名是select,就创建全选按钮 let th = document.createElement("th"); if (prop === "select") { let input = document.createElement("input"); input.type = "checkbox"; input.className = 'checkbox' input.checked = this.checkAll(); //由于当数据修改时绑定了createTable方法,每次数据改变就会刷新全选状态 input.that = this //将全局this存至元素中,方便后续调用 input.addEventListener("change", this.checkEvent); th.appendChild(input); } else { th.textContent = prop; } thr.appendChild(th) } for (let i = 0; i < this.shoppingList.length; i++) { let tr = document.createElement("tr"); this.table.appendChild(tr); for (let str in this.shoppingList[i]) { let td = document.createElement("td"); td.data = this.shoppingList[i]; //将每一行数据存至td元素中,方便后续调用 this.selectTdType(td, this.shoppingList[i], str); tr.appendChild(td); } } localStorage.shoppingList = JSON.stringify(this.shoppingList) //当表格渲染完毕就将数据存至本地缓存,浏览器关闭或刷新不会清除数据 }, selectTdType(td, data, type) { //将表格中的元素通过属性分类 switch (type) { case 'select': let input = document.createElement("input"); input.type = "checkbox"; input.checked = data.select; input.className = 'checkbox' input.that = this input.addEventListener("change", this.checkEvent); td.appendChild(input); break; case 'icon': let img = new Image(); img.src = data.icon; td.appendChild(img); break; case 'num': let countNum = this.createCountNum(td, data); countNum.input.value = data.num; break; case 'deleted': let btn = document.createElement("button"); btn.textContent = "删除"; td.appendChild(btn); btn.that = this btn.data = data; btn.addEventListener("click", this.deleteItemEvent); break; default: td.textContent = data[type]; break; } }, createCountNum(ele, data) { //创建数量计数器 let div = document.createElement("div"); //这个父元素用于储存单个商品信息 ele.appendChild(div); div.className = "numBox"; let leftBtn = this.createMark(div, 'reduce') //减少商品按钮 let input = document.createElement("input"); input.type = "text"; input.value = "1"; //商品初始数量 div.appendChild(input); let rightBtn = this.createMark(div, 'add') //新增商品按钮 div.input = input; div.data = data; div.that = this rightBtn.that = this rightBtn.data = data; leftBtn.addEventListener("click", this.reduceItemEvent); rightBtn.addEventListener("click", this.addItemEvent); input.addEventListener("input", this.inputItemEvent); input.addEventListener("blur", this.inputItemEvent); return div; }, createMark(parentEle, type) { //判断增加或减少键 let markBtn = document.createElement("button"); markBtn.textContent = type == "add" ? '+' : '-'; parentEle.appendChild(markBtn); return markBtn }, addItemEvent() { //第一个事件抛发(当用户点击列表商品或点击增加键时,抛发事件给document触发addItemEventHandler方法,用于参数传递,参数通过event对象进行传递) let _this = this.that let event = new Event(_this.ADD_ITEM_EVENT); event.data = this.data; event._this = _this; document.dispatchEvent(event); }, // 添加商品事件 /* 1、如果需要添加商品是新商品时候,就创建该商品对象 2、在表格里,使用for-in循环时,是根据对象属性创建先后来循环的 3、因此如下,我们给对象先添加一个select属性,这样在后面的循环中,第一个就可以创建出多选框了,紧接着将商品列表对应的对象的所有属性添加到这个对象中,再添加sum属性,并且计算sum的值 */ addItemEventHandler(e) { //当用户点击列表商品或点击增加键时执行函数 let _this = e._this let obj = { //这里遵循对象顺序,先定义的属性排在前面,所以将选择框放在最前定义,总价和删除键放在最后 select: false }; for (let str in e.data) { obj[str] = e.data[str]; } obj.sum = obj.num * obj.price; obj.deleted = false; _this.searchItemById(obj.id, _this.shoppingList, obj, 'add') _this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 }, reduceItemEvent() { //第二个事件抛发(当用户点击减少键时,抛发事件给document触发reduceItemEventHandler方法,用于参数传递,参数通过event对象进行传递) let _this = this.parentElement.that let event = new Event(_this.REDUCE_ITEM_EVENT); event.data = this.parentElement.data; event._this = _this; document.dispatchEvent(event); }, reduceItemEventHandler(e) { //与增加同理 let _this = e._this e.data.sum = e.data.num * e.data.price; _this.searchItemById(e.data.id, _this.shoppingList, e.data, 'reduce') _this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 }, deleteItemEvent() { //第三个事件抛发(当用户点击删除键时,抛发事件给document触发deleteItemEventHandler方法,用于参数传递,参数通过event对象进行传递) let _this = this.that let event = new Event(_this.DDELETE_ITEM_EVENT); event.data = this.data; event._this = _this; document.dispatchEvent(event); }, deleteItemEventHandler(e) { let _this = e._this _this.shoppingList = _this.shoppingList.filter(function(item) { //数组过滤函数,返回id属性不等于当前id的数组,即删除当前选中的对象,并重新赋值 return item.id !== e.data.id; }); _this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 localStorage.shoppingList = JSON.stringify(_this.shoppingList) //这里为了考虑,若删除到0条时,无法重新初始化表格(shoppingList.length==0,无法进入createTable函数) }, inputItemEvent(e) { //第四个事件抛发(当用户输入数值或处于失焦时,过滤数据,并抛发事件给document触发inputItemEventHandler方法,用于参数传递,参数通过event对象进行传递) let _this = this.parentElement.that if (e.type === "input") { this.value = this.value.replace(/[^0-9]/g, ""); //只允许输入数字 if (this.value === "0") { // 如果=0,就设为1 this.value = "1"; } if (this.value.length > 2) { // 如果输入的内容大于2位,让这个值为99(最大99个) this.value = "99"; } } else if (e.type === "blur") { if (this.value.length === 0) { // 失焦时,如果什么都没有输入,也设为1 this.value = "1"; } // 当失焦时才将事件抛发到document let event = new Event(_this.INPUT_ITEM_EVENT); event.num = this.value event.data = this.parentElement.data; event._this = _this; document.dispatchEvent(event); } }, inputItemEventHandler(e) { let _this = e._this _this.shoppingList.map(function(item) { //遍历查询被修改的对象 if (item.id === e.data.id) { item.num = e.num; item.sum = item.price * item.num; } }); _this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 }, checkAll() { //初始化表格时执行,查找所有选项是否都选中(全选框随其他选项框状态修改而修改) return this.shoppingList.filter(function(item) { return !item.select; }).length === 0; //返回true或false }, checkEvent() { //第五个事件抛发(当用户选中或取消任意选项框时,抛发事件给document触发checkItemEventHandler方法,用于参数传递,参数通过event对象进行传递) let _this = this.that let data = this.parentElement.data; let event = new Event(_this.CHECK_ITEM_EVENT); event.all = !data; //若没有data,说明是全选框 event.select = this.checked; //赋值给select属性 event._this = _this; event.data = data; document.dispatchEvent(event); }, checkItemEventHandler(e) { let _this = e._this if (e.all) { //若e.all为true说明该选项框是全选框 _this.shoppingList.map(function(item) { item.select = e.select; //其他选项框与全选框状态一致 }) } else { _this.shoppingList.map(function(item) { //单选,选中某一个(在表格初始化时执行checkAll判断是否全选) if (item.id === e.data.id) { item.select = e.select; } }) } _this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 }, searchItemById(id, list, data, type) { //遍历查询某项商品增加或减少 let arr = list.filter(function(item) { return item.id === id; }); //若有返回值则对某项商品操作(在1-99区间,若为0则直接删除) switch (type) { case "add": if (arr.length == 0) { list.push(data); } else if (arr[0].num < 99) { arr[0].num++; arr[0].sum = arr[0].num * arr[0].price; } break; case "reduce": if (arr[0].num > 1) { arr[0].num--; arr[0].sum = arr[0].num * arr[0].price; } else { this.shoppingList = list.filter(function(item) { return item.id !== id; }); this.createTable() //当数据发生变化时重新渲染购物车列表初始化购物车 localStorage.shoppingList = JSON.stringify(this.shoppingList) } break; } } } }()) ShopCar.prototype.constructor = ShopCar;
完整目录结构


浙公网安备 33010602011771号