浅析MVC

170 阅读5分钟

一、MVC是什么?

MVC(Model View Controller)是一种架构设计模式.

Mmodel,即数据层(数据模型),负责操作所有的数据。

Vview,即视图层,负责所有UI界面,是提供给用户的操作界面,是程序的外壳。

CController,即控制层,负责根据用户从“视图层”输入指令,选取“数据层中的数据”,对其进行相应的操作(绑定事件等),产生最终结果。

这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。

关于MVC,事实上没有一个明确的定义,我理解的MVC是将代码结构化的一种抽象的概念。

1.Model 数据层(数据模型)

//示例
let Model = {
  data: { 数据源 },
  create: { 增加数据 },MVC是什么? MVC(Model View Controller)是一种架构设计模式.
  delete: { 删除数据 },
  update(data) {
    Object.assign(m.data, data); //用新数据替换旧数据
    eventBus.trigger("m:update"); //eventBus触发'm:update'信息,通知View刷新界面
  },
  get: { 获取数据 },
};

2.View 视图层

//示例
let View={
    el:要刷新的元素,
    html:'要显示在页面上的刷新内容'
    init(){
        v.el:初始化需要刷新的元素
    },
    render(){
        刷新页面
    }
}

3.Controller 控制层

let Controller={
    init(){
        v.init()//初始化View
        v.render()//第一次渲染页面
        c.autoBindEvents()//自动的事件绑定
        eventBus.on('m:update',()=>{v.render()}//当enentsBus触发'm:update'是View刷新
    },
    events:{事件以哈希表的方式记录存储},
    //例如:
   events: {
    'click #add1': 'add',
    'click #minus1': 'minus',
    'click #mul2': 'mul',
    'click #divide2': 'div',
    },
    add() {
      m.update({n: m.data.n + 1})
    },
    minus() {
      m.update({n: m.data.n - 1})
    },
    mul() {
      m.update({n: m.data.n * 2})
    },
    div() {
      m.update({n: m.data.n / 2})
    },
    method(){
        data=新数据
        m.update(data) // controller 通知 model去更新数据
    },
    autoBindEvents(){
    	for (let key in c.events) { // 遍历events表,然后自动绑定事件
      const value = c[c.events[key]]
      const spaceIndex = key.indexOf(' ')
      const part1 = key.slice(0, spaceIndex) // 拿到 'click'
      const part2 = key.slice(spaceIndex + 1)  // 拿到'#add1'
      v.el.on(part1, part2, value)
    }
}

二、EventBus

1.EventBus是什么?

前面提到MVC三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。当层与层之间需要通信时,这时就需要用到EventBus。EventBus主要用于组件之间的监听与通信。

2.EventBus 常用API

EventBus.on()监听事件

EventBus.trigger()触发事件

EventBus.on()取消监听事件

eventBus.trigger('m:updated') //触发事件 

eventBus.on('m:updated',()=>{ //监听事件
     v.render(m.data.n)
 })

三、表驱动编程

表驱动编程(Table-Driven Methods)是一种编程模式。

适用场景:消除代码中频繁的 if else 或 switch case 的逻辑结构代码,使代码更加简化

  • 事实上,任何信息都可以通过表来挑选。在简单情况下用逻辑语句是更简单的,但是一旦判断条件增多,那可能要写大量重复的判断语句,这时候我们通过遍历表来实现条件判断,将事半功倍。

例1

需求:写一个函数,传入年月,返回对应天数

  • 闰年满足:(四年一润 且 百年不润) 或 (四百年再润)

常规写法

function getDay(year, month) {
  let isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 1 : 0
  if (month === 2) {
    return 28 + isLeapYear
  } else if (month===1||month===3||month===5||month===7||month===8||month===10||month===12) {
    return 31
  } else if (month === 4 || month === 6 || month === 9 || month === 11) {
    return 30
  }
}
console.log(getDay(2020, 12))  // 31
console.log(getDay(2020, 2))   // 29
console.log(getDay(2019, 2))   // 28
复制代码

表驱动写法

const monthDays = [
  [31,28,31,30,31,30,31,31,30,31,30,31],
  [31,29,31,30,31,30,31,31,30,31,30,31]
]
function getDay(year, month){
  let isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 1 : 0
  return monthDays[isLeapYear][month-1]
}
console.log(getDay(2020, 12)) // 31
console.log(getDay(2020, 2))  // 29
console.log(getDay(2019, 2))  // 28
复制代码

例2

监听元素绑定事件

常规写法

add1(){ ... }
min1(){ ... }
mul2(){ ... }
div2(){ ... }
document.querySelector('#add1').addEventListener('click', add1)
document.querySelector('#min1').addEventListener('click', min1)
document.querySelector('#mul2').addEventListener('click', mul2)
document.querySelector('#div2').addEventListener('click', div2)
document........
复制代码

表驱动写法

const controller = {
  add1(){  },
  min1(){  },
  mul2(){  },
  div2(){  },
  events: { // 表驱动编程(对象)
    "click #add1": "add1", // key 的前半为要监听的事件,后半为监听的元素,value 为要执行的方法
    "click #min1": "min1",
    "click #mul2": "mul2",
    "click #div2": "div2"
  },
  autoBindEvents() {
    for(let key in this.events){ // 遍历对象获得对应的 key 去做赋值操作
      const handler = this[this.events[key]]
      const [event, selector] = key.split(" ")  // ["click", "#min1"]
      $("容器").on(event, selector, handler) // 将提取出来的值去监听事件
    })
  }
}

「常规写法」的代码虽然更简单直白,但代码过于重复。随着数据规模的增大,如果监听事件有10个100个,那么这种写法的代码量也在加剧

「表驱动编程」让代码具有一个稳定的复杂度,不论数据规模多大,都能保持简单。

  • 拒绝重复,保持稳定的简单,这才是程序员所追求的

四、关于模块化

MDN文档:JavaScript modules 模块

1.模块化

这里的模块化值得是前端模块化模块化MVC的重要前提。模块化就是把相对独立的代码从一大段代码里抽取成一个个小模块每个模块之间相对独立,方便日后的维护。

ES6 的语法里引入了 Import 和 export 就是用来实现模块化。

export default c; // 默认导出
export { c }; // 另外一种导出方式。记得要加花括号

复制代码

2.最小知识原则

以最少的知识使用一个模块,平时我们需要在 html 里引入 css 和 js,现在只需要引入一个模块 js 就够了。

3.M+V+C

每个模块都可以用M+V+C的模式搞定,即使MVC在vue 里浓缩成了一个 V。

4.事不过三(抽象思维)

把重复的事情抽象简单

  • 重复的代码==>抽象成函数
  • 同样的属性==>抽象成原型或类
  • 同样的原型==>使用继承