MVC知识梳理

294 阅读6分钟

1.MVC是什么?

  • M就是model数据模型,放数据相关的代码
  • V就是view视图层,放用户界面相关代码
  • C就是Controller控制器,放除M和C外的其他操作层面的代码,包括监听用户事件等
  • MVC是设计模式中DRY原则的体现:Don't Repeat Yourself
  • MVC就是万金油:所有的页面都可以用MVC来优化结构

1.1 Model数据模型

//伪代码示例
const M={
   data:{数据}
   create:{增}
   delete:{删}
   update(data){
     Object.assign(m.data,data) 
     evenBus.trigger('m:update')
     }
     get:{查}
   }

1.2 View视图层

//伪代码示例
const V={
   el:元素,容器}
   html:
   init(){
     v.el:初始化需要刷新的元素
     }
     render(){
       渲染页面
     }
     
   }

1.3 Controller控制器

  • c往往和v混在一起
//以1.4的实例示例
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})
    },
    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)
    }
}

1.4 MVC实例

  • 目标:做一个加减乘除计算器
  • 每次点击加减乘除按钮,数值就会变,基本思想就是监听click事件

1.gif

  • MVC代码
import './app1.css'
import $ from 'jquery'

const eventBus = $(window)
// 数据相关都放到m
const m = {
  data: {
    n: parseInt(localStorage.getItem('n'))
  },
  create() {},
  delete() {},
  update(data) {
    Object.assign(m.data, data)
    eventBus.trigger('m:updated')
    localStorage.setItem('n', m.data.n)
  },
  get() {}
}
// 视图相关都放到v
const v = {
  el: null,
  html: `
  <div>
    <div class="output">
      <span id="number">{{n}}</span>
    </div>
    <div class="actions">
      <button id="add1">+1</button>
      <button id="minus1">-1</button>
      <button id="mul2">*2</button>
      <button id="divide2">÷2</button>
    </div>
  </div>
`,
  init(container) {
    v.el = $(container)
  },
  render(n) {
    if (v.el.children.length !== 0) v.el.empty()
    $(v.html.replace('{{n}}', n))
      .appendTo(v.el)
  }
}
// 其他都c
const c = {
  init(container) {
    v.init(container)
    v.render(m.data.n) // view = render(data)
    c.autoBindEvents()
    eventBus.on('m:updated', () => {
      console.log('here')
      v.render(m.data.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) {
      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)
    }
  }
}

export default c

2.EventBus

  • eventBus主要用于对象间通信
  • 使用eventBus可以满足最小知识原则,m和v相互不知道对方的细节,却可以调用对方的功能
  • eventBus提供了on、off和trigger等API,on用于监听事件,trigger用于触发事件
  • 代码示例
// M数据模型更新时,会 trigger 触发一个事件
const m = {
  ....
  update(data) {
    Object.assign(m.data, data)
    eventBus.trigger('m:updated')  // 通知一下view层,已更新了数据,view该开始工作了
    localStorage.setItem('n', m.data.n)
  },
  ....
}
//controller会用 on  监听事件, 然后通知 view 模型去重新渲染页面
const c = {
  init(container) {
    v.init(container)
    v.render(m.data.n) // view = render(data)
    c.autoBindEvents()
    eventBus.on('m:updated', () => {   // controller会用 on  监听事件,
      //然后通知 view 模型去重新渲染页面
      console.log('here')
      v.render(m.data.n)
    })
  },
  ... 
}

3.表驱动编程

  • 当我们需要判断3种以上的情况,做出相应的事情,往往需要写很多很多的If else,这样的代码可读性不强
  • 举例:在上面的例子中,加减乘除四个按钮我需要分别判断是哪一个按钮被点击,再修改output的值
  • 按照传统做法, 我们会对四个按钮分别绑定click事件,然后再分别写四个回调函数,修改值
  • 表驱动程序应用场景:当你看到大批类似但不重复的代码
  • 眯起眼睛,看看到底哪些才是重要的数据
  • 把重要的数据做成哈希表,你的代码就简单了
  • 这是数据结构知识给我们的红利
  • 代码示例
// 绑定加减乘除按钮的父元素,就只用一个事件监听器 ;用哈希表存下按钮和按钮对应的操作
const c = {
  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)
    }
  }

4.关于模块化

  • 模块化就是把相对独立的代码从一大段代码里抽取成一个个短小精悍的模块
  • 每个模块之间相对独立,方便以后的维护和修改
  • ES6的语法里引入了Import和export就是用来实现模块化的
  • 当我们在app1.js 里封装好了controller 模型, 然后导出controller:
export default c  // 默认导出
export {c} // 另外一种导出方式。记得要加花括号
  • 在Main.js里我们想用controller:
import x from './app1.js'
等价于import {default as x} from './app1.js'

x.init('#app1')

5.MVC带来的关于编程抽象思维的启发

抽象思维1 最小知识原则

  • 引入一个模块需要引入 html、css、js
  • 引入一个模块需要引入 html、js
  • 引入一个模块需要引入 js
  • 你需要知道的知识越少越好
  • 模块化为这一点奠定了基础
  • 代价:这样做会使得页面一开始是空白的,没内容没样式
  • 解决方法:加菊花、加骨架、加占位内容等,也有人选择用 SSR 技术来解决

抽象思维2 以不变应万变

  • 既然每个模块都可以用 m + v + c 搞定
  • 那么每个模块我就都这样写就好啦
  • 不用再思考类似的需求该怎么做了 -代价:有时候会有一些多余的用不到代码,有时候遇到特殊情况不知道怎么变通,比如没有 html 的模块怎么做 mvc

抽象思维3 表驱动编程

  • 见文章part3

抽象思维4 事不过三

  • 同样的代码写三遍,就应该抽成一个函数
  • 同样的属性写三遍,就应该做成共用属性(原型或类)
  • 同样的原型写三遍,就应该用继承
  • 代价:有的时候会造成继承层级太深,无法一下看懂代码
  • 解决方法:可以通过写文档、画类图解决

抽象思维5 俯瞰全局

  • 把所有的对象看成点
  • 一个点和一个点怎么通信
  • 一个点和多个点怎么通信
  • 多个点和多个点怎么通信
  • 最终我们找出一个专用的点负责通信,这个点就是 event bus(事件总线)

抽象思维6

  • view = render(data)
  • 比起操作 DOM 对象,直接 render 简单多了
  • 只要改变 data,就可以得到对应的 view
  • 代价:render 粗犷的渲染肯定比 DOM 操作浪费性能
  • 解决方法:用虚拟 DOM 能让 render 只更新该更新的地方

6.跨学科联系

  • 信息论:帧间压缩算法interframe compression
  • 所谓帧,就是影像动画最小单位的单幅静止画面,连续播放——运动画面——视频。视频需要压缩,否则带宽无法承受。假设:一个2h未压缩的19201080高清视频,刷新率是25(人眼特性每秒需要能够刷新20帧以上 才会感觉流畅),用的是RGB三原色 1个像素占3个字节。那么:2h60min60s25帧19201080像素*3(每像素字节数)=11198GB(B代表字节而非比特b 1B=8b)网络视频需要压缩,甚至几千上万倍依然看起来流畅。
  • 通过关注信息增量而非信息存量来极大提升效率。
  • 如下图,一个人招手的画面分解成每一帧的样子。对每张图进行压缩,很多信息是重复的。图上这个人除了右手在变化其他部位基本不变,所以只需要对第一个图进行整体处理,在接下来的几张图里只处理他变化的那只手的信息,就可以大大减少工作量——帧间压缩算法的精髓,变魔法似的压缩几千倍。

1.jpg