• JS案例:如何用300行原生js代码写出高大上的购物车


    购物车可谓是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/methods.js"></script>
        <script src="./js/shop.js"></script>
    </head>
    
    <body>
        <ul id="shopBox" class="shopBox"></ul>
        <script>
            var shopCar
            Methods.AjaxTool('get', './json/shop.json', {}, function (res) {//Methods封装的AjaxTool是用于本地请求数据的ajax方法
                // var a = singleCar()
                // var b = singleCar()
                // console.log(a === b) //单例模式下,若执行两次,其两次结果为true
                singleCar().init(JSON.parse(res), shopBox); //入口函数
            })
    
            function singleCar() {//单例模式,只产生一个实例
                if (!shopCar) {
                    shopCar = new ShopCar()
                }
                return shopCar
            }
        </script>
    </body>
    
    </html>

    CSS:(不做过多注释)

    * {
        margin: 0;
        padding: 0;
    }
    
    .shopBox {
        overflow: hidden;
         1000px;
        margin: 50px auto 0;
    }
    
    .liItem {
        float: left;
        list-style: none;
        padding: 10px;
         150px;
        height: 200px;
        text-align: center;
        border: 1px solid lightcoral;
    }
    
    .liItem img {
         100px;
        height: 100px;
    }
    
    .leftBtn,
    .rightBtn {
         30px;
        height: 30px;
        background: white;
        border: 1px solid black;
        font-size: 25px;
        line-height: 30px;
    }
    
    .text {
         50px;
        height: 26px;
        display: inline-block;
        vertical-align: bottom;
        text-align: center;
    }
    
    table {
        font-size: 30px;
         1200px;
        border: 1px solid lightcoral;
        border-collapse: collapse;
        margin: 50px auto;
    }
    
    .checkbox {
         30px;
        height: 30px;
    }
    
    td {
        border: 1px solid lightcoral;
        text-align: center;
        vertical-align: middle;
    }
    
    td button {
         150px;
        height: 60px;
    }
    
    .numBox {
         150px;
        height: 30px;
        margin: auto;
        position: relative;
    }
    
    .numBox>button {
         40px;
        height: 42px;
        background-color: white;
        border: 1px solid #000000;
    }
    
    .numBox>input {
         70px;
        height: 40px;
        border: 1px solid #000000;
        border-left: none;
        border-right: none;
        text-align: center;
    }

    JSON(用于存放商品信息):

    [{
            "id": 1001,
            "icon": "img/1.png",
            "name": "餐饮0",
            "num": 1,
            "price": 10
        },
        {
            "id": 1002,
            "icon": "img/2.png",
            "name": "餐饮1",
            "num": 1,
            "price": 20
        },
        {
            "id": 1003,
            "icon": "img/3.png",
            "name": "餐饮2",
            "num": 1,
            "price": 30
        },
        {
            "id": 1004,
            "icon": "img/4.png",
            "name": "餐饮3",
            "num": 1,
            "price": 40
        },
        {
            "id": 1005,
            "icon": "img/5.png",
            "name": "餐饮4",
            "num": 1,
            "price": 50
        },
        {
            "id": 1006,
            "icon": "img/6.png",
            "name": "餐饮5",
            "num": 1,
            "price": 60
        },
        {
            "id": 1007,
            "icon": "img/7.png",
            "name": "餐饮6",
            "num": 1,
            "price": 70
        },
        {
            "id": 1008,
            "icon": "img/8.png",
            "name": "餐饮7",
            "num": 1,
            "price": 80
        },
        {
            "id": 1009,
            "icon": "img/9.png",
            "name": "餐饮8",
            "num": 1,
            "price": 90
        },
        {
            "id": 1010,
            "icon": "img/10.png",
            "name": "餐饮9",
            "num": 1,
            "price": 100
        }
    ]

    JS文件(第一个是自定义方法集合,第二个是购物车的全部逻辑)

    methods.js:

    var Methods = (function () {//通过立即执行函数return对象的方式产生闭包,用于存放私有变量
        // 可添加私有变量
        return {
            // ajax请求方法
            AjaxTool(method, url, data, fn) {
                var xhr;
                if (window.ActiveXObject) { //ie浏览器
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } else if (window.XMLHttpRequest) { //其他浏览器
                    xhr = new XMLHttpRequest();
                }
                if (method == 'get') {
                    url = this.urlJoin(url, data)
                    data = null
                }
                xhr.open(method, url);
                xhr.send(data ? JSON.stringify(data) : '')
                xhr.addEventListener('load', function () {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        fn(this.response)
                    } else {
                        fn('err')
                    }
    
                })
            },
            //将对象拼接到url中
            urlJoin(url, obj) {
                var list = []
                for (var key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        list.push(`${key}=${obj[key]}`)
                    }
                }
                return `${url}?${list.join('&')}`
            }
        }
    }())

    shop.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) { //创建商品详情列表
                var data = this.shopList
                for (var i = 0; i < data.length; i++) {
                    var li = document.createElement("li");
                    li.className = "liItem";
                    var img = new Image();
                    img.src = data[i].icon;
                    li.appendChild(img);
                    var title = document.createElement("div");
                    title.textContent = data[i].name;
                    li.appendChild(title);
                    var price = document.createElement("span");
                    price.textContent = data[i].price + "元";
                    li.appendChild(price);
                    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);
                var thr = document.createElement("tr");
                this.table.appendChild(thr);
                for (var prop in this.shoppingList[0]) { //创建表头,如果属性名是select,就创建全选按钮
                    var th = document.createElement("th");
                    if (prop === "select") {
                        var 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 (var i = 0; i < this.shoppingList.length; i++) {
                    var tr = document.createElement("tr");
                    this.table.appendChild(tr);
                    for (var str in this.shoppingList[i]) {
                        var 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':
                        var 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':
                        var img = new Image();
                        img.src = data.icon;
                        td.appendChild(img);
                        break;
                    case 'num':
                        var countNum = this.createCountNum(td, data);
                        countNum.input.value = data.num;
                        break;
                    case 'deleted':
                        var 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) { //创建数量计数器
                var div = document.createElement("div"); //这个父元素用于储存单个商品信息
                ele.appendChild(div);
                div.className = "numBox";
                var leftBtn = this.createMark(div, 'reduce') //减少商品按钮
                var input = document.createElement("input");
                input.type = "text";
                input.value = "1"; //商品初始数量
                div.appendChild(input);
                var 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) { //判断增加或减少键
                var markBtn = document.createElement("button");
                markBtn.textContent = type == "add" ? '+' : '-';
                parentEle.appendChild(markBtn);
                return markBtn
            },
            addItemEvent() { //第一个事件抛发(当用户点击列表商品或点击增加键时,抛发事件给document触发addItemEventHandler方法,用于参数传递,参数通过event对象进行传递)
                var _this = this.that
                var event = new Event(_this.ADD_ITEM_EVENT);
                event.data = this.data;
                event._this = _this;
                document.dispatchEvent(event);
            },
            addItemEventHandler(e) { //当用户点击列表商品或点击增加键时执行函数
                var _this = e._this
                var obj = { //这里遵循对象顺序,先定义的属性排在前面,所以将选择框放在最前定义,总价和删除键放在最后
                    select: false
                };
                for (var 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对象进行传递)
                var _this = this.parentElement.that
                var event = new Event(_this.REDUCE_ITEM_EVENT);
                event.data = this.parentElement.data;
                event._this = _this;
                document.dispatchEvent(event);
            },
            reduceItemEventHandler(e) { //与增加同理
                var _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对象进行传递)
                var _this = this.that
                var event = new Event(_this.DDELETE_ITEM_EVENT);
                event.data = this.data;
                event._this = _this;
                document.dispatchEvent(event);
            },
            deleteItemEventHandler(e) {
                var _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对象进行传递)
                var _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
                    var event = new Event(_this.INPUT_ITEM_EVENT);
                    event.num = this.value
                    event.data = this.parentElement.data;
                    event._this = _this;
                    document.dispatchEvent(event);
                }
            },
            inputItemEventHandler(e) {
                var _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对象进行传递)
                var _this = this.that
                var data = this.parentElement.data;
                var 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) {
                var _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) { //遍历查询某项商品增加或减少
                var 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;

    附上运行效果:

  • 相关阅读:
    冲刺第五天个人博客
    冲刺第四天个人博客
    典型用户及场景
    冲刺第三天个人博客
    冲刺第二天个人博客
    冲刺第一天个人博客
    第三周学习进度表
    第二周学习进度表
    webServices
    vs开发工具使用问题
  • 原文地址:https://www.cnblogs.com/HelloWorld-Yu/p/12438917.html
Copyright © 2020-2023  润新知