浅析 MVC

73 阅读3分钟
一、MVC

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

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

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

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

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

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

Model 数据层(数据模型)

let Model = { 
  data: { 数据源 }, 
  create: { 增加数据 },  
  delete: { 删除数据 },   
  update(data) {   
    Object.assign(m.data, data); //用新数据替换旧数据 
    eventBus.trigger("m:update"); //eventBus触发'm:update'信息,通知View刷新界面   }, 
    get: { 获取数据 }
 };

View 视图层

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

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

EventBus主要用于各个对象之间的通信。

EventBus主要有三个API:

  • on:用于监听对象
  • trigger:用于触发事件
  • off:用于取消对象监听

当我们想让M的数据更新后,V视图也能跟着改变的时候,就可以用到EventsBus。

代码示例:const eventBus = $(window) //获取EventBus对象
const m = {
  data: {
    ...
  },
  update(data) {
    // trigger 触发事件更新数据
    eventBus.trigger('m:updated')
  }
}

const v = {
  render(data) {
    ...更新数据
  }
} 

const c={ 
 eventBus.on('m:updated', () => {  // on方法执行监听后触发 v 视图渲染
      console.log('here')
      v.render(m.data.n)
    })
三、表驱动编程

表驱动编程的意义在于逻辑与数据的分离。( 类似于事件委托 )

主要在于把重复的代码省去,让需要变化的代码留下。

例如下面这个代码,v.render()执行了多次

    bindEvents() {
        //绑定鼠标事件,绑定在最外面(#app1)上面,监听祖父元素,事件委托
        v.container.on('click', '#add1', () => {
            m.data.n += 1
            v.render()
        })
        v.container.on('click', '#minus1', () => {
            m.data.n -= 1
            v.render()
        });
        v.container.on('click', '#mul2', () => {
            m.data.n *= 2
            v.render()
        });
        v.container.on('click', '#divide2', () => {
            m.data.n /= 2
            v.render()
        });

    }

这样的代码可以用表驱动编程改写成如下形式:

 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})
  },
  autoBindEvents() { //自动绑定事件
    for (let key in c.events) {
      const value = c[c.events[key]]
      const spaceIndex = key.indexOf(' ') 
      const part1 = key.slice(0, spaceIndex) 
      const part2 = key.slice(spaceIndex + 1)
      v.el.on(part1, part2, value)   
    }
  }
}

这样做的好处就是减少了代码重复,并且逻辑更清晰,不需要依赖DOM,同时增加了可扩展性。

四、如何理解模块化

模块化是将代码封装起来放在独立的模块中,只留下必要的接口方便供给他人使用。

为什么要模块化?

  1. 维护性高,它的每个封装模块都是独立的。
  2. 需要改编或提出其他需求,不会影响其他文件内容。
  3. 复用性高,一个封装的模块能够被多次调用,省去重复编写时间。