学习框架前的预备思想:MVC

294 阅读5分钟

什么是MVC?

  • M :model, 即数据模型,负责数据相关的任务,包括对数据的增删改查
  • V :view, 即视图层,即用户能看得到的界面
  • C :Controller,控制器,负责监听用户事件,然后调用 M 和 V 更新数据和视图

关于MVC,没有一个明确的定义,每个人心目中都有不同的MVC,下面根据我的理解,给出MVC的伪代码示例。

Model 数据模型

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

View 视图层

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

Controller 控制器

控制器通过绑定时间,根据用户的操作,调用M与V更新视图与数据

假设我们现在需要做一个简单的加减乘除器

加减乘除

就可以书写这样的控制器来实现控制

const c ={
    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})
    },
    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主要是用于对象之间通信,MVC的三层是互相独立的,它们不知道彼此的存在,所以当它们之间需要通信时,就要用到EventBus。

EventBus主要API

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

如下:

const m = {
  ...
  update(data) {
   ...
    eventBus.trigger('m:updated') 
   ...
  },
  ...
}

我们在m层中触发事件,更新数据

const c = {
  init(container) {
   	...
    eventBus.on('m:updated', () => { 
      v.render(m.data.n)
    })
  },
  ... 
}

c层就会通过on监听到这个事件,并通知v层去把页面重新渲染

表驱动编程

当我们需要判断多种情况来执行事件时,往往需要很多的if…else,这样会使代码看上去很不优雅,且如果判断中相同的语句太多,更是会显得代码臃肿,所以我们可以构造一个哈希表,把多个条件存入中,从而提升代码的可读性,这也就是表驱动编程

如上方的加减乘除例子,如果我们使用普通的方法,

$button1.on("click", () => {
  let n = parseFloat($number.text());
  n += 1;
  localStorage.setItem("n", n);
  $number.text(n);
});
$button2.on("click", () => {
  let n = parseFloat($number.text());
  n -= 1;
  localStorage.setItem("n", n);
  $number.text(n);
});
$button3.on("click", () => {
  let n = parseFloat($number.text());
  n *= 2;
  localStorage.setItem("n", n);
  $number.text(n);
});
$button4.on("click", () => {
  let n = parseFloat($number.text());
  n /= 2;
  localStorage.setItem("n", n);
  $number.text(n);
});

这样的代码,重复度极高,可读性不强

于是,我们使用表驱动编程

 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) { // 遍历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)
    }
}

用这种方法,绑定按钮的父元素,用哈希表存下按钮与按钮对应的操作,之后再把表遍历来实现功能,大大增加代码的可读性。虽然在这个例子中似乎代码更加复杂了,但是事件越多它的简洁性就会越显著。

模块化

模块化是MVC的重要前提,把相对独立的代码从一大段代码里抽取成一个个短小的模块,每个模块相对独立,方便以后的更新和维护,这就是模块化。

ES6中,引入了import与export来实现模块化

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

在另一个模块中,将其引入

import x from './app1.js'
{default as x} from './app1.js'//另一种引入方法

具体详见-->JavaScript modules 模块 - JavaScript | MDN (mozilla.org)

划分模块的一个准则是"高内聚低耦合"

从模块粒度来看,

  • 高内聚:尽可能类的每个成员方法只完成一件事(最大限度的聚合);

  • 低耦合:减少类内部,一个成员方法调用另一个成员方法。

从类角度来看,

  • 高内聚低耦合:减少类内部,对其他类的调用;

从功能块来看

  • 高内聚低耦合:减少模块之间的交互复杂度(接口数量,参数数据)

总结

MVC的抽象思维

1.最小知识原则

  • 你需要知道的知识越少越好
  • 模块化为这一点奠定了基础

2.以不变应万变

  • 既然每个模块都可以用m+v+c搞定
  • 那么每个模块都这样写就好了
  • 不用再考虑类似的需求该怎么做了

3.表驱动编程

  • 当有大批代码类似带不重复
  • 把重要的数据做成哈希表,这样可以大大简化代码

4.事不过三

  • 同样的代码写三遍,就应该抽象成一个函数
  • 同样的属性写三遍,就应该做成共有属性(原型或类)
  • 同样的原型写三遍,就应该用继承

5.俯瞰全局

把所有对象看成点

  • 一个点与一个点怎么通信
  • 一个点与多个点怎么通信
  • 多个点与多个点怎么通信
  • 最终我们找出一个专用的点负责通信
  • 这个点就是EventBus

6.view = render(data)

  • 只要改变data,就可以得到对应的view
  • vue中并没有体现这个思想,但React中有体现