JS案例:用购物车理解前端MVC架构

简介: JS案例:用购物车理解前端MVC架构

什么是MVC:

Model View Controller即:模型-视图-控制器

通俗来讲,在编程语言中,Model就是数据,可以理解为数据库,View就是显示数据的外观,Controller是用来连接前两者的行为,常见的Vue采用的是M-V-VM架构,与MVC类似,但是基于MVC


MVC的作用:

说到作用,就不得不提面向对象与面向过程的区别了


面向过程就是,将解决问题的思路流程一步一步进行,紧扣在一起,最终达到结果


面向对象,是将某个问题的解决方式剥离开,其目的不是为了完成某个步骤,而是将某个事物(对象)的角色(属性)和行为(方法)作为核心


说了这些,到底MVC有什么好处呢?


举个栗子:A是某公司的一位前端程序员,平时用面向过程进行编程,这天,好不容易完成了手头上的活,准备回家,这时,产品经理走过来,让他改个小地方,这下就完了,面向过程的思维使他的代码环环相扣,代码耦合性强,内聚性高,密不可分,改一个地方就要几乎全改


A的哥哥也是一个前端程序员,平时用面向对象编程,产品经理让他改一个效果,由于用的面向对象,他的代码没有层次感,通用的方法全部提取出来,使得代码耦合性低,想改哪直接改相关的类或者方法就好了


当然,在小型项目中无法体现它的优点,甚至会小题大做,大材小用,而在大型项目中,其耦合性低,代码复用性高,搭建相对较快


如何使用MVC架构:

又是这个购物车,业余时间用MVC做了一个简单的购物车:


目录结构大致是这样


2.png


购物车整体流程:


       目录结构将model view controller剥离开


       Modedl层:存储数据,显示数据


       View:根据Model数据渲染页面


       Controller:传递数据


       Command:操作数据,获取数据


       Event:事件总线,注册事件


       商品列表:


           初始化View层,建立Ajax获取数据,之后由controller触发事件至事件总线,然后再由注册的事件将ajax数据传至Model中完成商品列表初始化


           当model获取到商品列表数据时,通过代理set()  触发新建商品列表事件,通过command操作view达到新建列表目的


       购物车表格:


           当用户对view进行操作时,触发注册的事件,通过command修改Model中的数据(购物车列表)从而再由command驱动view中的刷新表格进行渲染


效果:

1.gif


以下是所有代码:

源码地址:https://gitee.com/DieHunter/myCode/tree/master/shopCarMVC

后端(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 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]
        }
    }
}


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)
    }
}


总结

总的来说,MVC架构思想是许多语言在大型项目上常常运用的设计理念,其提升代码复用,降低耦合性,使其在大型项目上焕发光彩,其实不难发现,生活中的事物和MVC有着相似的共同点,代码源于生活,代码也能创造生活


相关文章
|
2月前
|
前端开发 算法 Java
【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
上下文选择器(迭代选择器):基于祖先或同胞元素选择一个元素 ID和类选择器:基于id#和class的属性值进行选择元素。 属性选择器:基于属性的有无和特征进行选择。 ①上下文选择器: 上下文选择器的语法格式:标签1 标签2{属性:值;} //注意:组合选择器和上下文选择器的区别,组合选择器以逗号隔开, 上下文选择器以空格隔开 ②特殊的上下文选择器 子选择器> : 语法格式:标签1>标签2 解释说明:标签1和标签2
241 1
|
7月前
|
人工智能 监控 前端开发
基于 Next.js 的书法字体生成工具架构设计与 SSR 优化实践
本项目是一款书法字体生成工具,采用 Next.js 14(App Router)与 Tailwind CSS 构建前端,阿里云 Serverless 部署后端。通过混合渲染策略(SSG/SSR/CSR)、Web Worker 异步计算及 CDN 字体分片加载优化性能。服务端借助阿里云函数计算处理计算密集型任务,将平均耗时从 1200ms 降至 280ms,支持 1000+ QPS。动态路由与 ARMS 监控提升工程化水平,未来计划引入 WebGPU 和 AI 字体风格迁移技术,进一步优化用户体验。
|
10月前
|
人工智能 JavaScript 安全
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
479 13
【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
|
10月前
|
JavaScript 前端开发 索引
40个JS常用使用技巧案例
大家好,我是V哥。在日常开发中,JS是解决页面交互的利器。V哥总结了40个实用的JS小技巧,涵盖数组操作、对象处理、函数使用等,并附带案例代码和解释。从数组去重到异步函数,这些技巧能显著提升开发效率。先赞再看后评论,腰缠万贯财进门。关注威哥爱编程,全栈开发就你行!
299 16
|
9月前
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
9月前
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
前端开发 JavaScript
前端一键回到顶部案例
本文介绍了如何实现网页中的一键回到顶部功能,包括两种方法:第一种是通过HTML中的锚点跳转实现快速回到顶部;第二种是使用JavaScript的`scrollTo`方法结合`requestAnimationFrame`实现滚动动画效果,让页面滚动更加平滑自然。
481 1
前端一键回到顶部案例
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
2309 0
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
前端开发
【前端web入门第五天】03 清除默认样式与外边距问题【附综合案例产品卡片与新闻列表】
本文档详细介绍了CSS中清除默认样式的方法,包括清除内外边距、列表项目符号等;探讨了外边距的合并与塌陷问题及其解决策略;讲解了行内元素垂直边距的处理技巧;并介绍了圆角与盒子阴影效果的实现方法。最后通过产品卡片和新闻列表两个综合案例,展示了所学知识的实际应用。
339 11
|
JSON 前端开发 JavaScript
socket.io即时通信前端配合Node案例
本文介绍了如何使用socket.io库在Node.js环境下实现一个简单的即时通信前端配合案例,包括了服务端和客户端的代码实现,以及如何通过socket.io进行事件的发送和监听来实现实时通信。
307 2

热门文章

最新文章