浅析 MVC

1,964 阅读5分钟

一、MVC是什么

M:模型(Model):数据保存

V:视图(View):用户界面。

C:控制器(Controller):业务逻辑

一个软件的实现可以分成3个部分,他们相互之间可以通信,根据他们的通信方式可以衍生出其他的几种设计模式

传统意义上的MVC各个部分之间的通信是单向的

  • View 传送指令到 Controller

  • Controller 完成业务逻辑后,要求 Model 改变状态

  • Model 将新的数据发送到 View,用户得到反馈

各个部分的伪代码实现

Model

Model = {
    data: { 程序需要操作的数据或信息 },
    create: { 增数据 },
    delete: { 删数据 },
    update(data) { 
       Object.assign(m.data, data) //使用新数据替换旧数据
       eventBus.trigger('m:upate') // eventBus触发'm:update'信息, 通知View刷新 
    },
    get:{ 获取数据 } 
}

View

View = {
    el: 需要刷新的元素,
    html: `<h1>M V C</h1>....显示在页面上的内容`
    init(){
        v.el: 需要刷新的元素
    },
    render(){ 刷新页面 }
}

Controller

Controller = {
   init(){
      v.init() // View初始化
      v.render() // 第一次渲染
      c.autoBindEvents() // 自动的事件绑定
      eventBus.on('m:update', () => { v.render() }) // 当eventBus触发'm:update'时View刷新
   },
   events:{ 事件以哈希表方式记录 },
   method() {
      data = 改变后的新数据
      m.update(data)
   },
   autoBindEvents() { 自动绑定事件 }
}EventBus

MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

其中model和view之间不再通信,而是通过presenter这个中介来实现交互,此时代码大部分分布在presenter上

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 没有区别。

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。

二、eventBus

DOM 的事件机制就是发布订阅模式最常见的实现,这大概是前端最常用的编程模型了,监听某事件,当该事件发生时,监听该事件的监听函数被调用。eventBus就是实现发布订阅模式的一种方法。

这里可以看下发布订阅和观察者模式的小区别

zhuanlan.zhihu.com/p/51357583

其实jquery和vue构造出来的对象都有监听事件的方法,我们只需要构造一个对象,然后调用他的on 和 trigger方法就可以实现组件之间的通信

下面介绍几个vue常用的事件api及其内部实现

1.$on 事件的订阅

$on(eventName,callback)  
//参数1:事件名称  参数2:事件函数
//判断当前事件名称是否存在,如果不存在则创建一个key值为事件名称
//value为一个数组 将callback push到数组中
 
const eventList = {};
const $on = (eventName,callback)=>{
     if(!eventList[eventName]){
         eventList[eventName] = [];
     }
     eventList[eventName].push(callback)
}

2.$off 事件的解绑

$off(eventName,[callback])  
//参数1:事件名称  参数2:[事件函数]
//判断当前事件名称是否存在,如果存在继续判断第二个参数是否存在,如果存在则找到相//对应的下标 然后将函数在数组中移除
//如果不存在则将整个数组清空
 
const eventList = {};
const $off = (eventName,callback)=>{
    if(eventList[eventName]){
          if(callback){
                 let index = eventList[eventName].indexOf(callback);
                 eventList[eventName].splice(index,1)
           }
    }else{
           eventList[eventName].length = 0; 
    }
}
 
export default = {
     $on,
     $emit,
     $off
 }

3.$emit 事件的触发

$emit(eventName,[params]) 
//参数1:事件名称 参数2:[需要传递的参数]
//判断当前事件的名称是否存在,如果存在则遍历数组,得到所有的函数,
//并执行。然后将params当做实参传递到函数中去
const eventList = {};
const $emit = (eventName,params)=>{
    if(eventList[eventName]){
         let arr = eventList[eventName];
         arr.map((cb)=>{
             cb(params)
         })
    }
}

三、表驱动编程

我们先看一个简单例子来感受下表驱动,由于看了很多博客都是非要整的很复杂的代码,这样让人的关注点都在理解代码本身上了,所以我自己写了个很简单的例子

function age(name){
    if(name==="小明"){
        console.log("年龄是"+10)
    }else if(name==="小白"){
        console.log("年龄是"+14)
    }else if(){
       
    }
    //....等等
}

假如我们要写一个查询年龄的函数,如果用if else语句写,那么有多少人我们就得写多少个if else语句,这样我们的代码量是线性增长的,如果我们用一个哈希表来记录这些映射关系会怎样

const list={
    "小明":10,
    "小白":14,
    //等等...
}
function age2(name){
    if(name in list){
        console.log(name+"的年龄是"+list[name])
    }else{
        console.log("查无此人")
    }
}

这样我们的数据部分就和代码部分分离了,函数主体只是去表中查找然后输出结果,这就是表编程的基本思想,在我看来这就是编程的基本思想,从我们发明计算机的那一刻就是为了能减少重复的工作,正所谓懒才是人类进化的源驱动力。。。。

其实这里我还行扯一下《西部世界》中的一个钢琴,“密尔顿自动演奏钢琴”

现在感觉这个跟表编程的概念好吻合的,我只是给你一个乐谱,实现的函数(钢琴)不会发生改变,而你可以随意的自定义乐谱。

四、我眼中的模块化编程

其实现在的开发所形成出的模块化也是一种懒的体现,为了实现代码的可复用性,便于管理以及可移植性,以及防止作用域冲突等,模块化是历史的必然,因为总有人会厌倦日复一日的重复相同的东西,所以就衍生出了MVC,把一个页面上的不同部分的功能分离,把一个功能的数据,视图,控制分离,都只是为了“偷懒”。

总结一点技术性的模块化知识吧

前流行的模块化规范有CommonJSAMDCMDES6import/export

  • CommonJS的主要实践者就是nodejs,一般对模块输出用module.exports去输出,用require去引入模块,CommonJS一般采用同步加载【require / module.exports / exports】
  • AMD遵从RequireJs规范,推崇依赖前置(提前执行)【require / defined】
  • CMD遵从SeaJs规范,推崇依赖就近(延迟执行)【require / defined】
  • ES6 可静态分析,提前编译,不是在运行时确认【import / export】