JS案例:用購物車理解前端MVC架構

目錄javascript

什麼是MVC:css

MVC的做用:html

如何使用MVC架構:前端

效果:java

如下是全部代碼:node

後端(nodejs):程序員

server.jsajax

data.js(存放商品列表)數據庫

前端express

shopCar.html(入口頁面)

shop.css

JS文件夾:

bussiness

command

components

config

controller

event

model

utils

view


什麼是MVC:

Model View Controller即:模型-視圖-控制器
通俗來說,在編程語言中,Model就是數據,能夠理解爲數據庫,View就是顯示數據的外觀,Controller是用來鏈接前二者的行爲,常見的Vue採用的是M-V-VM架構,與MVC相似,可是基於MVC

MVC的做用:

說到做用,就不得不提面向對象與面向過程的區別了

面向過程就是,將解決問題的思路流程一步一步進行,緊扣在一塊兒,最終達到結果

面向對象,是將某個問題的解決方式剝離開,其目的不是爲了完成某個步驟,而是將某個事物(對象)的角色(屬性)和行爲(方法)做爲核心

說了這些,到底MVC有什麼好處呢?

舉個栗子:A是某公司的一位前端程序員,平時用面向過程進行編程,這天,好不容易完成了手頭上的活,準備回家,這時,產品經理走過來,讓他改個小地方,這下就完了,面向過程的思惟使他的代碼環環相扣,代碼耦合性強,內聚性高,密不可分,改一個地方就要幾乎全改

A的哥哥也是一個前端程序員,平時用面向對象編程,產品經理讓他改一個效果,因爲用的面向對象,他的代碼沒有層次感,通用的方法所有提取出來,使得代碼耦合性低,想改哪直接改相關的類或者方法就行了

固然,在小型項目中沒法體現它的優勢,甚至會小題大作,大材小用,而在大型項目中,其耦合性低,代碼複用性高,搭建相對較快

如何使用MVC架構:

又是這個購物車,業餘時間用MVC作了一個簡單的購物車:

目錄結構大體是這樣

購物車總體流程:

        目錄結構將model view controller剝離開

        Modedl層:存儲數據,顯示數據

        View:根據Model數據渲染頁面

        Controller:傳遞數據

        Command:操做數據,獲取數據

        Event:事件總線,註冊事件

        商品列表:

            初始化View層,創建Ajax獲取數據,以後由controller觸發事件至事件總線,而後再由註冊的事件將ajax數據傳至Model中完成商品列表初始化

            當model獲取到商品列表數據時,經過代理set()  觸發新建商品列表事件,經過command操做view達到新建列表目的

        購物車表格:

            當用戶對view進行操做時,觸發註冊的事件,經過command修改Model中的數據(購物車列表)從而再由command驅動view中的刷新表格進行渲染

效果:

如下是全部代碼:

後端(nodejs):

server.js

/*
 *後端採用node+express搭建一個簡單的接口,經過本地數據,將商品列表傳至前端
 * 
 */
const express = require('express');
const path = require('path');
const app = express();
const shopData = require('./data/shopData.js')
let serverToken = 'hello'
app.all("*", function (req, res, next) { //跨域
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "content-type");
    res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
    next();
});
app.use('/getShopList', function (req, res) {
    let data = req.query
    if (!checkToken(data.token)) { //簡單獲取前端token,校驗
        res.send({
            result: 0,
            msg: 'token fail'
        })
        return
    }
    res.send({
        result: 1,
        msg: 'success',
        type: 'getShopList',
        shopData
    })
})

function checkToken(teken) {
    return teken == serverToken
}
app.use('/img', express.static(path.join(__dirname, './img'))); //後端目錄靜態化,用url+img訪問文件夾
app.use('/client', express.static(path.join(__dirname, '../client')));
app.listen(1024, "127.0.0.1", function () {
    console.log("服務開啓,開始偵聽");
});

data.js(存放商品列表)

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

前端

shopCar.html(入口頁面)

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>shopCar</title>
	<link rel="stylesheet" href="./src/style/shop.css">
</head>

<body>
	<script type="module">
		/*
		購物車總體流程:
		目錄結構將model view controller剝離開
		Modedl層:存儲數據,顯示數據
		View:根據Model數據渲染頁面
		Controller:傳遞數據
		Command:操做數據,獲取數據
		Event:事件總線,註冊事件
		商品列表:
		 	初始化View層,創建Ajax獲取數據,以後由controller觸發事件至事件總線,而後再由註冊的事件將ajax數據傳至Model中完成商品列表初始化
		 	當model獲取到商品列表數據時,經過代理set()  觸發新建商品列表事件,經過command操做view達到新建列表目的
		購物車表格:
		 	當用戶對view進行操做時,觸發註冊的事件,經過command修改Model中的數據(購物車列表)從而再由command驅動view中的刷新表格進行渲染
		*/
		import ShopView from './src/js/view/ShopView.js'
		// 實例化View層入口函數
		new ShopView()
	</script>
</body>

</html>

shop.css

* {
    margin: 0;
    padding: 0;
}

.shopBox {
    overflow: hidden;
    width: 1000px;
    margin: 50px auto 0;
}

.liItem {
    float: left;
    list-style: none;
    padding: 10px;
    width: 150px;
    height: 200px;
    text-align: center;
    border: 1px solid lightcoral;
}

.liItem img {
    width: 100px;
    height: 100px;
}

.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: 30px;
    width: 1200px;
    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: 150px;
    height: 60px;
}

.numBox {
    width: 150px;
    height: 30px;
    margin: auto;
    position: relative;
}

.numBox>button {
    width: 40px;
    height: 42px;
    background-color: white;
    border: 1px solid #000000;
}

.numBox>input {
    width: 70px;
    height: 40px;
    border: 1px solid #000000;
    border-left: none;
    border-right: none;
    text-align: center;
}

JS文件夾:

bussiness

  • Ajax.js
    import ShopEvent from '../event/ShopEvent.js'
    import Utils from '../utils/Utils.js'
    import Api from '../config/Api.js'
    import ShopController from '../controller/ShopController.js'
    export default class Ajax {//Ajax類,用於請求後端或本地數據
        // Ajax請求函數
        static AjaxTool(method = Api.GET, url, data) {
            let xhr;
            if (window.ActiveXObject) { //ie瀏覽器
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            } else if (window.XMLHttpRequest) { //其餘瀏覽器
                xhr = new XMLHttpRequest();
            }
            url = Api.URL + Api.PORT + Api.PATH + url
            if (method !== Api.POST) {
                method = Api.GET
                url = Utils.urlJoin(url, data)
                data = null
            } else {
                method = Api.POST
            }
            xhr.open(method, url);
            xhr.send(data ? JSON.stringify(data) : '')
            xhr.addEventListener('load', Ajax.loadHandler) //Ajax類是靜態類,沒法使用this
        }
        static loadHandler(e) {
            //this指向xhr
            let xhr = e.currentTarget;
            if (xhr.readyState === 4 && xhr.status === 200) {
                Ajax.data = xhr.response
            } else {
                Ajax.data = 'error'
            }
    
        }
        static set data(value) { //使用set對象代理模式替代請求數據回調函數(只寫set表示data只可寫入,不可讀取)
            let res = JSON.parse(value)
            switch (res.result) {
                case 1:
                    console.log(res.msg)
                    ShopController.dispatch(ShopEvent.GET_DATA, res)//獲取到數據後不作其餘操做,將數據經過事件拋出至Event總線中
                    break;
                case 0:
                    console.log('加載失敗')
                    console.log(res.msg)
                    break;
                default:
                    break;
            }
        }
    }

     

command

  • MainCommand(command彙總)
    import GetDataCommand from '../command/GetDataCommand.js'
    import CreateListCommand from '../command/CreateListCommand.js'
    import CreateTableCommand from '../command/CreateTableCommand.js'
    import AddItemCommand from '../command/AddItemCommand.js'
    import DelItemCommand from '../command/DelItemCommand.js'
    import ReduceItemCommand from '../command/ReduceItemCommand.js'
    import ChangeItemCommand from '../command/ChangeItemCommand.js'
    import SelectItemCommand from '../command/SelectItemCommand.js'
    export default {
        GetDataCommand,
        CreateListCommand,
        CreateTableCommand,
        AddItemCommand,
        DelItemCommand,
        ReduceItemCommand,
        ChangeItemCommand,
        SelectItemCommand
    }

     

  • GetDataCommand(獲取商品列表)
    import ShopModel from '../model/ShopModel.js'
    export default class GetDataCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() {
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            ShopModel.getInstance().shopList = data.shopData//將ajax獲取的數據發送到Model
        }
    }

     

  • CreateListCommand(建立商品列表)
    import CreateList from '../view/CreateList.js'
    export default class CreateListCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() {//建立商品列表
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            for (let i = 0; i < data.length; i++) {
                let createList = new CreateList(document.body)
                createList.shopList = data[i]
            }
        }
    }

     

  • CreateTableCommand(建立購物車表格)
    import CreateTable from '../view/CreateTable.js'
    export default class ShopCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() {//刷新購物車表格
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            let createTable = new CreateTable(document.body)
            createTable.shoppingList = data
        }
    }

     

  • AddItemCommand(增長商品)
    import ShopModel from '../model/ShopModel.js'
    export default class AddItemCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() { //新增商品
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            AddItemCommand.addItem(ShopModel.getInstance().shoppingList, data)
        }
        static addItem(list, data) { //遍歷查詢某項商品增長或減小
            let arr = list.filter(function (item) {
                return item.id === data.id;
            }); //如有返回值則對某項商品操做(在1-99區間,若爲0則直接刪除)
            if (arr.length == 0) {
                data.num++;
                data.sum = data.num * data.price;
                list.push(data);
            } else if (arr[0].num < 99) {
                arr[0].num++;
                arr[0].sum = arr[0].num * arr[0].price;
            }
            ShopModel.getInstance().shoppingList = list
        }
    }

     

  • ReduceItemCommand(減小商品)
    import ShopModel from '../model/ShopModel.js'
    export default class ReduceItemCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() { //減小商品
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            ReduceItemCommand.reduceItem(ShopModel.getInstance().shoppingList, data)
        }
        static reduceItem(list, data) { //遍歷查詢某項商品增長或減小
            let arr = list.filter(function (item) {
                return item.id === data.id;
            }); //如有返回值則對某項商品操做(在1-99區間,若爲0則直接刪除)
            if (arr[0].num > 1) {
                arr[0].num--;
                arr[0].sum = arr[0].num * arr[0].price;
            } else {
                data.num = 0; //此處初始化model中的shopList,不然會假刪除(刪除棧中的數量)
                list = list.filter(function (item) {
                    return item.id !== data.id;
                });
            }
            ShopModel.getInstance().shoppingList = list
        }
    }

     

  • ChangeItemCommand(修改商品數量)
    import ShopModel from '../model/ShopModel.js'
    export default class ChangeItemCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() { //修改商品數量
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            ChangeItemCommand.changeItem(ShopModel.getInstance().shoppingList, data)
        }
        static changeItem(list, data) {
            let arr = list.filter(function (item) {
                return item.id === data.id;
            });
            arr[0].sum = arr[0].num * arr[0].price;
            ShopModel.getInstance().shoppingList = list
        }
    }

     

  • DelItemCommand(刪除商品)
    import ShopModel from '../model/ShopModel.js'
    export default class DelItemCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() { //刪除商品
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            DelItemCommand.delItem(ShopModel.getInstance().shoppingList, data)
        }
        static delItem(list, data) { //遍歷查詢某項商品增長或減小
            data.num = 0; //此處初始化model中的shopList,不然會假刪除(刪除棧中的數量)
            data.select = false; //此處初始化model中的shopList,不然會假刪除(刪除棧中的數量)
            ShopModel.getInstance().shoppingList = list.filter(function (item) { //數組過濾函數,返回id屬性不等於當前id的數組,即刪除當前選中的對象,並從新賦值
                return item.id !== data.id;
            });
        }
    }

     

  • SelectItemCommand(選中商品)
    import ShopModel from '../model/ShopModel.js'
    export default class SelectItemCommand { //行爲類,用於執行ctrl層經過事件的方式發來的指令
        constructor() {
    
        }
        eventHandler(e) { //使用動態方法,而不是static靜態方法,由於該方法將被使用屢次,使用new AddShopCommand實例化後調用
            let {
                data
            } = e
            SelectItemCommand.selItem(ShopModel.getInstance().shoppingList, data)
        }
        static selItem(list, data) { //遍歷查詢某項商品增長或減小
            if (!data) { //全選框
                list.checkedAll = !list.checkedAll
                list.map(function (item) {
                    item.select = list.checkedAll; //其餘選項框與全選框狀態一致
                })
            } else { //單選框
                list.checkedAll = 1 //計數器,用來查詢是否爲全選狀態
                list.map(function (item) { //單選,選中某一個(在表格初始化時執行checkAll判斷是否全選)
                    if (item.id === data.id) {
                        item.select = !item.select
                    }
                    list.checkedAll *= item.select
                })
            }
            ShopModel.getInstance().shoppingList = list
        }
    }

     

components

  • Counter(計數器組件)
    import ShopEvent from '../event/ShopEvent.js'
    import ShopController from '../controller/ShopController.js'
    import Utils from '../utils/Utils.js'
    export default class Counter { //計數器組件
        constructor(_data, _parentEle) {
            this.data = _data
            this.parentEle = _parentEle
            this.ele = this.createCounter()
        }
        createCounter() { //建立數量計數器
            let div = Utils.createEle('div', {}, {
                className: 'numBox'
            })
            this.parentEle.appendChild(div);
            let leftBtn = this.createMark(div, 'reduce') //減小商品按鈕
            let input = Utils.createEle('input', {}, {
                type: 'text',
                value: this.data.num
            })
            div.appendChild(input);
            let rightBtn = this.createMark(div, 'add') //新增商品按鈕
            leftBtn.addEventListener("click", this.reduceItemEvent);
            rightBtn.addEventListener("click", this.addItemEvent);
            input.addEventListener("input", Utils.throttle(this.changeItemEvent, 500)); // 節流
            return div;
        }
        createMark(parentEle, type) { //判斷增長或減小鍵
            let markBtn = Utils.createEle('button', {}, {
                textContent: type == "add" ? '+' : '-'
            })
            parentEle.appendChild(markBtn);
            return markBtn
        }
        addItemEvent = e => { //新增商品時,拋發事件至command控制model修改數據,刷新表格
            ShopController.dispatch(ShopEvent.ADD_SHOPIING_ITEM, this.data)
        }
        reduceItemEvent = e => { //減小商品
            ShopController.dispatch(ShopEvent.REDUCE_SHOPIING_ITEM, this.data)
        }
        changeItemEvent = e => { //修改商品
            e.target.value = this.data.num = this.checkNumber(e.target.value)
            ShopController.dispatch(ShopEvent.CHANGE_SHOPIING_ITEM, this.data)
        }
        checkNumber(value) { //過濾數據
            value = value.replace(/[^0-9]/g, ""); //只容許輸入數字
            if (value === "0") { // 若是=0,就設爲1
                value = "1";
            }
            if (value.length > 2) { // 若是輸入的內容大於2位,讓這個值爲99(最大99個)
                value = "99";
            }
            if (value.length === 0) { //  若是什麼都沒有輸入,也設爲1
                value = "1";
            }
            return value
        }
    }

     

config

  • Api
    export default class Api {//接口配置類
        static URL = "http://127.0.0.1";
        static PORT = ":1024";
        static PATH = '/'
        static GET = "get";
        static POST = "post";
        static IMGPATH = Api.URL + Api.PORT + Api.PATH;
        static ServerApi = {
            getShopList: 'getShopList' //獲取商品列表
        }
    }

     

controller

  • ShopController(控制層,作事件傳導,數據傳輸)
    export default class ShopController extends EventTarget { //控制層,處理用戶交互,路由,輸入,將model view controller剝離開,經過controller中的事件監聽拋發進行路由傳輸數據
        constructor() { //繼承事件對象,用於拋發自定義事件
            super();
        }
        static get instance() {  //單例寫法與java中getinstance類似,new會生成一個新對象,分配內存,而這麼寫能夠把一個已存在的引用給你使用,節省效能,若只使用get + 屬性名而不用set產生只讀屬性,只容許調用,沒法修改
            if (!ShopController._instance) {
                Object.defineProperty(ShopController, "_instance", {
                    value: new ShopController()
                })
            }
            return ShopController._instance;
        }
        static dispatch(type, data) { //拋發自定義事件,傳遞數據
            let event = new Event(type)
            event.data = data
            ShopController.instance.dispatchEvent(event)
        }
        static runCommand(type, Command) { //觀察者模式,當自定義事件觸發時調用其餘類中的方法,與dispatch對應,相似於addEventlistener,只不過將回調函數換成類中的動態方法
            var command = new Command()
            ShopController.instance.addEventListener(type, command.eventHandler)
        }
    }

     

event

  • ShopEvent
    export default class ShopEvent {
        constructor() {
    
        }
        // 全部自定義事件名稱
        static GET_DATA = 'get_data'
        static GET_SHOP_LIST = 'get_shop_list'
        static GET_SHOPIING_LIST = 'get_shopping_list'
        static ADD_SHOPIING_ITEM = 'add_shopping_item'
        static DEL_SHOPIING_ITEM = 'del_shopping_item'
        static REDUCE_SHOPIING_ITEM = 'reduce_shopping_item'
        static CHANGE_SHOPIING_ITEM = 'change_shopping_item'
        static SELECT_SHOPIING_ITEM = 'select_shopping_item'
    }

     

  • EventGroup(事件總線)
    import ShopEvent from './ShopEvent.js'
    import ShopController from '../controller/ShopController.js'
    import MainCommand from '../command/MainCommand.js'
    let {
        GetDataCommand,
        CreateListCommand,
        CreateTableCommand,
        AddItemCommand,
        DelItemCommand,
        ReduceItemCommand,
        ChangeItemCommand,
        SelectItemCommand
    } = MainCommand
    export default class EventGroup { //事件總線,註冊全部model層與其它層的業務邏輯,全程經過controller層中的事件機制進行通訊
        constructor() {
            /*
            1.Ajax獲取到數據後,觸發GetDataCommand中的方法,用於傳遞數據至Model層中,而後經過Model層調用CreateListCommand創造商品列表
            2.當用戶對商品作任何操做時,都會修改Model從而觸發CreateTableCommand,如下操做會觸發CreateTableCommand
            3.點擊商品列表或點擊商品加號按鈕時觸發AddItemCommand,經過AddItemCommand修改model中的數據,從而驅動CreateTableCommand
            4.點擊商品減號按鈕時觸發ReduceItemCommand,修改model中的數據,從而驅動CreateTableCommand
            5.點擊商品刪除按鈕時觸發DelItemCommand,刪除model中的數據,從而驅動CreateTableCommand
            6.修改商品數量時觸發ChangeItemCommand,更新model中的數據,從而驅動CreateTableCommand
            7.選中商品時觸發SelectItemCommand,更新model中的數據,從而驅動CreateTableCommand
             */
            ShopController.runCommand(ShopEvent.GET_DATA, GetDataCommand)//獲取商品列表數據
            ShopController.runCommand(ShopEvent.GET_SHOP_LIST, CreateListCommand)//新建商品列表
            ShopController.runCommand(ShopEvent.GET_SHOPIING_LIST, CreateTableCommand)//刷新購物車表格
            ShopController.runCommand(ShopEvent.ADD_SHOPIING_ITEM, AddItemCommand)//商品新增或數量加一
            ShopController.runCommand(ShopEvent.DEL_SHOPIING_ITEM, DelItemCommand)//商品刪除
            ShopController.runCommand(ShopEvent.REDUCE_SHOPIING_ITEM, ReduceItemCommand)//商品數量減一
            ShopController.runCommand(ShopEvent.CHANGE_SHOPIING_ITEM, ChangeItemCommand)//修改商品數量
            ShopController.runCommand(ShopEvent.SELECT_SHOPIING_ITEM, SelectItemCommand)//選擇商品
        }
    }

     

model

  • ShopModel(模型層,用於數據存放及數據邏輯)
    import ShopEvent from '../event/ShopEvent.js'
    import ShopController from '../controller/ShopController.js'
    export default class ShopModel { //模型層,用於數據存放及數據邏輯,經過事件處理機制(controller)傳遞數據,再由command進行對數據操做
        constructor() {
            this._shopList = null
            this._shoppingList = []
        }
    
        static getInstance() { //單例寫法與java中getinstance類似,new會生成一個新對象,分配內存,而這麼寫能夠把一個已存在的引用給你使用,節省效能,若只使用get + 屬性名而不用set產生只讀屬性,只容許調用,沒法修改
            if (!ShopModel._instance) {
                Object.defineProperty(ShopModel, "_instance", {
                    value: new ShopModel()
                })
            }
            return ShopModel._instance;
        }
        set shopList(value) {//設置商品列表
            this._shopList = value;
            ShopController.dispatch(ShopEvent.GET_SHOP_LIST, value)
        }
        get shopList() {
            return this._shopList
        }
        set shoppingList(value) {//數據修改時,驅動view進行表格刷新
            this._shoppingList = value;
            ShopController.dispatch(ShopEvent.GET_SHOPIING_LIST, value)
        }
        get shoppingList() {
            return this._shoppingList
        }
    }

     

utils

  • Utils(工具類)
    export default class Utils { //工具類
    	//將對象拼接到url中
    	static urlJoin(url, obj) {
    		var list = []
    		for (var key in obj) {
    			if (obj.hasOwnProperty(key)) {
    				list.push(`${key}=${obj[key]}`)
    			}
    		}
    		return `${url}?${list.join('&')}`
    	}
    	static createEle(ele, style, attribute) { //新增標籤,設置屬性及樣式
    		let element = document.createElement(ele)
    		if (style) {
    			for (let key in style) {
    				element.style[key] = style[key];
    			}
    		}
    		if (attribute) {
    			for (let key in attribute) {
    				element[key] = attribute[key];
    			}
    		}
    		return element
    	}
    	// 函數節流
    	static throttle(fn, time) {
    		let _timer = null
    		return function () {
    			if (_timer) {
    				clearTimeout(_timer)
    				_timer = null
    			}
    			_timer = setTimeout(fn.bind(this, ...arguments), time)
    		}
    	}
    }

     

view

  • ShopView(視圖層,用於元素渲染,顯示數據)
    import Api from '../config/Api.js'
    import AJAX from '../bussiness/Ajax.js'
    import EventGroup from '../event/EventGroup.js'
    export default class ShopView { //視圖層,用於元素渲染,顯示數據,依據(model)模型數據建立
        constructor() {
            new EventGroup() //註冊全部自定義事件,用於數據傳輸
            AJAX.AjaxTool(Api.GET, Api.ServerApi.getShopList, { //請求服務端購物車列表
                token: 'hello'//發送從後端獲取的token用於驗證,此處未作獲取,直接用一個字符代替
            })
        }
    }

     

  • CreateList(列表視圖)
    import Api from '../config/Api.js'
    import Utils from '../utils/Utils.js'
    import ShopEvent from '../event/ShopEvent.js'
    import ShopController from '../controller/ShopController.js'
    
    export default class CreateList { //視圖層,用於元素渲染,顯示數據,依據(model)模型數據建立
        constructor(parentEle) {
            this.parentEle = parentEle
            this._shopList = null
        }
        set shopList(value) { //使用對象代理,每當數據發生更改時渲染商品列表
            if (this._shopList) {
                this.createListEle(this._shopList, this.parentEle)
                return;
            }
            this._shopList = value
            this.createListEle(value, this.parentEle)
        }
        get shopList() {
            return this._shopList
        }
        createListEle(data, parentEle) {
            let li = Utils.createEle('li', {}, {
                'className': 'liItem'
            })
            let img = Utils.createEle('img', {}, {
                'src': Api.IMGPATH + data.icon
            })
            let title = Utils.createEle('div', {}, {
                'textContent': data.name
            })
            let price = Utils.createEle('span', {}, {
                'textContent': data.price + "元"
            })
            li.appendChild(img);
            li.appendChild(title);
            li.appendChild(price);
            li.addEventListener('click', this.addItemEvent)
            parentEle.appendChild(li);
        }
        addItemEvent = e => { //當用戶點擊添加商品時,將數據經過controller層發送至事件總線,再驅動model層並修改數據,後續由Model提供數據刷新表格
            ShopController.dispatch(ShopEvent.ADD_SHOPIING_ITEM, this.shopList)
        }
    }

     

  • CreateTable(購物車表格視圖)
    import Api from '../config/Api.js'
    import Utils from '../utils/Utils.js'
    import Counter from '../components/Counter.js'
    import ShopEvent from '../event/ShopEvent.js'
    import ShopController from '../controller/ShopController.js'
    
    export default class CreateTable { //視圖層,用於元素渲染,顯示數據,依據(model)模型數據建立
        constructor(parentEle) {
            this.parentEle = parentEle
            this._shoppingList = null
            this._table = null
        }
        set shoppingList(value) { //對象代理,若model數據改變時,刷新表格
            let table = document.getElementById('table')
            if (table) { //初始化表格,如有則刪除
                table.remove()
                table = null;
            }
            this._shoppingList = value || []
            this.createTab(value, this.parentEle)
        }
        get shoppingList() {
            return this._shoppingList
        }
        createTab(data, parentEle) {
            this._table = Utils.createEle('table', {}, {
                id: "table"
            })
            let thr = Utils.createEle('tr')
            for (let prop in data[0]) { //建立表頭,若是屬性名是select,就建立全選按鈕
                let th = Utils.createEle('th')
                if (prop === "select") {
                    let input = Utils.createEle('input', {}, {
                        type: "checkbox",
                        className: "checkbox",
                        checked: this.checkedAll() //查詢是否全選
                    })
                    input.addEventListener("change", this.seleteEvent);
                    th.appendChild(input);
                } else {
                    th.textContent = prop;
                }
                thr.appendChild(th)
            }
            this._table.appendChild(thr)
            for (let i = 0; i < data.length; i++) {
                let tr = Utils.createEle('tr')
                this._table.appendChild(tr);
                for (let str in this.shoppingList[i]) {
                    let td = document.createElement("td");
                    this.selectTdType(td, this.shoppingList[i], str);
                    tr.appendChild(td);
                }
            }
            parentEle.appendChild(this._table)
        }
        selectTdType(td, data, type) {
            switch (type) {
                case 'select':
                    let input = Utils.createEle('input', {}, {
                        type: 'checkbox',
                        className: 'checkbox',
                        checked: data['select'],
                        data
                    })
                    input.addEventListener("change", this.seleteEvent);
                    td.appendChild(input);
                    break;
                case 'icon':
                    let img = Utils.createEle('img', {}, {
                        'src': Api.IMGPATH + data.icon
                    })
                    td.appendChild(img);
                    break;
                case 'num':
                    new Counter(data, td) //實例化計數器組件
                    break;
                case 'delete':
                    let btn = Utils.createEle('button', {}, {
                        textContent: '刪除',
                        data
                    })
                    td.appendChild(btn);
                    btn.addEventListener("click", this.deleteItemEvent);
                    break;
                default:
                    td.textContent = data[type];
                    break;
            }
        }
        deleteItemEvent = e => { //觸發刪除事件,經過command刪除後通知model驅動view進行刷新
            ShopController.dispatch(ShopEvent.DEL_SHOPIING_ITEM, e.target.data)
        }
        seleteEvent = e => { //觸發選擇商品事件,經過command刪除後通知model驅動view進行刷新
            ShopController.dispatch(ShopEvent.SELECT_SHOPIING_ITEM, e.target.data)
        }
        checkedAll() {
            let count = 1
            this.shoppingList.map((item) => {
                count *= item.select
            })
            return count
        }
    }
相關文章
相關標籤/搜索