M,V,C

MVC是什么

MVC是一种经典的软件架构模式,从学习Java、andriod到学习JavaScript,都遇到过它,再一次来学习它的概念:

MVC全称Model–view–controller,将应用程序分为三个部分:Model(模型)View(视图)Controller(控制)

  • Model(模型):负责管理应用程序的数据
  • View(视图):负责对Model层中的数据进行展示
  • Controller(控制):负责用户和应用程序之间的交互

在前端中,model层就是管理数据,实现数据的增删改查等操作,view层负责DOM等操作导致model层的数据改变后,重新渲染页面

Controller层负责连接model和view,它接受用户输入(例如鼠标单击或文本输入)并处理用户交互的回调。

下面通过一个简陋的小的应用程序来了解MVC的具体使用:

下面四个按钮对上面的数值进行加减乘除操作,再将结果显示在页面上,可以将这个程序分为MVC三个模块:

Model:负责从localstorage数据获取和将数据更新到localstorage

const m = {
    //从localstorage数据获取
    data: {
        result: parseInt(localStorage.getItem('data'))
    },
    //数据更新到localstorage
    update(data) {
        Object.assign(m.data, data)
        eventBus.trigger("updated")
        localStorage.setItem('data', m.data.result)
    }

}

View:负责将model层的数据和HTML渲染到页面

const v = {
    //视图的元素
    el: null,
    //包裹元素的容器
    html: `
    <div>
    <div class="result">
        <span id="output">{{n}}</span>
    </div>
    <div class="button">
    <button  id="add1">+1</button>
    <button  id="min1">-1</button>
    <button  id="mul2">*2</button>
    <button  id="div2">/2</button>
    </div>
    </div>
    `,
    //初始化
    init(container) {
        v.el = $(container) //将容器保存起来
        v.render()
    },
    //渲染+更新
    render(n) {
        if (v.el.children.length !== 0) v.el.empty();
        v.el = $(v.html.replace('{{n}}', n)).appendTo($(v.el))
    },
}

Controller:负责为元素绑定事件,监听用户的点击按钮操作,通过回调函数更新model数据,再让view层渲染到页面

const c = {
    init(container) {
        v.init(container) 
        v.render(m.data.result) //view=render(m.data.result)//视图等于渲染数据
        c.autoBindEvent()
        eventBus.on("updated", () => {
            v.render(m.data.result)
        })
    },
    v.el.on("click", "#add1", () => {
    m.update({
        result: m.data.result + 1
        })
    })
    v.el.on("click", "#min1", () => {
        m.update({
            result: m.data.result - 1
        })
    })
    v.el.on("click", "#mul2", () => {
        m.update({
            result: m.data.result * 2
        })
    })
    v.el.on("click", "#div2", () => {
        m.update({
            result: m.data.result / 2
        })
    })
}

可以观察到代码中使用了自定义的eventBus来完成Controller层和Model层之间的通信,下面就来聊一聊各模块之间的通信工具——EventBus

EventBus

EventBus 称为事件总线,它的核心思想是发布订阅者模式,我们可以通过调用EventBus 的接口来注册发送事件或接收事件,能达到任意模块之间的通信。

初始化

如何自定义使用eventBus呢?首先要进行初始化

import $ from "jquery"
const eventBus = $(window)

通过JQuery,把eventBus放在全局

触发事件

 eventBus.trigger(eventName, data)

在上个MVC小程序中可以看到 ,M层进行localStorage的数据更新操作后就会触发事件名为updated的事件,EventBus就会传达给监听这个事件的模块,进行通信。

监听事件

 eventBus.on(eventName, () => {
            //操作
        })

在C层监听到事件名为updated的事件发生后就会触发回调函数,通知V层渲染更新M层的新数据。

取消监听

 eventBus.off(eventName, () => {
            //操作
        })

也可以通过off这个API来取消监听

表驱动编程

表驱动方法编程(Table-Driven Methods)是一种编程模式,简单讲是指用查表的方法获取值。适用场景:消除代码中频繁的if else或switch case的逻辑结构代码,使代码更加直白.

现在通过利用表驱动方法来优化MVC小程序C层中的绑定事件代码:

const c = {
   ……
    events: {
        "click #add1": "add",
        "click #min1": "min",
        "click #mul2": "mul",
        "click #div2": "div"
    },
    add() {
        m.update({
            result: m.data.result + 1
        })
    },
    min() {
        m.update({
            result: m.data.result - 1
        })
    },
    mul() {
        m.update({
            result: m.data.result * 2
        })
    },
    div() {
        m.update({
            result: m.data.result / 2
        })
    },
    autoBindEvent() {
        for (let key in c.events) {
            const space = key.indexOf(" ")
            const part1 = key.slice(0, space)
            const part2 = key.slice(space + 1)
            const value = c[c.events[key]]
            console.log(value);
            v.el.on(part1, part2, value);
        }
    },

对比之前的代码,表驱动方法编程的优势便体现出来了:

1、可读性更强,自动绑定事件,一目了然。

2、更容易修改,要增加新的绑定事件,只要修改数据即可,不需要修改流程。

3、重用,第一种方法的只有绑定的事件名称、监听元素和回调函数不一样,逻辑都是一样的,产生了很多重复的代码。表驱动方法编程将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。

模块化

模块化是将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起

块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

模块化的方法降低代码耦合度,减少重复代码,提高代码重用性,并且在项目结构上更加清晰,便于维护。

通过构造一个简单的MVC模式的小应用让我学习了模块的封装。上例MVC小程序中的我们已经将程序划分为三个对象并完成了模块的封装,只需把C层对象导出,其他页面引入C对象,通过我们在暴露的init方法初始化传给C对象一个容器,就可以添加使用这个计算小程序,而不需要知道其内部的具体实现。

目前最常使用的两种模块化规范就是 ESM (ES Module)和CommonJS

ESM (ES Module)

语法规则

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 , export2 } from "module-name";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
// 等 ...

export let name1, name2, …, nameN; // also var, const
export { name1, name2, …, nameN };
export default function (…) { … } // also class, function*
export { name1, name2, …, nameN } from …;
// 等 ...

CommonJS

语法规则

'use strict';

var lodash = require('lodash');

const result = lodash.every(
  [true, 1, null, 'yes'],
  Boolean,
);

module.exports = result;

相比 esm 主要差别在语法上:

  • esm
    • import a from 'b'
    • export default c
  • cjs
    • const a = require('b')
    • module.exports = c