MVC

146 阅读2分钟

MVC:每个模块都可以写成三个对象

M-Model 数据模型 负责操作所有数据

V-View 视图 负责所有UI界面

C-Controller 控制器 负责其他

//数据放在m
const m = {
 //1.有个数据本数
  data: {},
  //2.可以对数据增删改查
  create() {},
  delete() {},
  update(data) {},
  get() {}
}
//视图放在v
const v = {
  //1、一个空容器,以后就是装html的容器
  el: null,
  //2、要添加的html
  html: ,
  //3、初始化容器函数,参数是我们给的要当容器的元素(应该是index.html里就有的元素)
  init(container) {v.el = $(container);},
  //4、渲染函数,参数将是数据。也就是视图全都是对数据渲染 view = render(data)
  render(x) {}
}
//其他放在c
const c = {
  //1.总初始化函数,参数是我们给的要当容器的元素
  init(container) {},
  //2、自动绑定事件
  //(1)把所有事件写成哈希表
  events: {},
  //(2)每个事件要执行的函数写出来
  add() {},
  minus() {},
  mul() {},
  div() {},
  //(3)自动绑定事件
  autoBindEvents() {}
}


复制代码

MVC就是个设计模式,设计模式就是一串优秀的代码,取个名字而已。设计模式的存在就是减少重复代码

MVC用于所有页面优化代码,减少重复

不学MVC你就会写出spaghetti code(面条式代码,烂代码),你就会成为外包程序员,只会重复自己

MVC是我们从面条代码到框架代码的必经之路,vue和react都是从MVC演变而来,只有知道了MVC,才能知道vue和react的原理

抽象思维(MVC设计模式总结,vue)

最小知识原则

  1. 引入一个模块需要引入html、css、 js
  2. 引入一个模块需要引入html、js
  3. 引入一个模块需要引入js
  4. 你需要知道的知识越少越好,模块化为这一点奠定了基础
  5. 模块化:在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块
  6. 例如:都要由main.js引入css reset 、整个页面的css、每个模块的js
    每个模块的js引入jquery 、 他的css 、他的html
    这样html里面就是空空的

但是有代价

  • 所以刚开始加载时白的,只有等js全加载完才会显示在页面上。用户看到白的超过三秒就想关掉了
  • (所以前端测试不能用很好的网络,否则你永远不知道你的用户的体验有多差)(开发者工具->network→online,slow 3g→Disable cache 可以查看)
  • 解决方法
  1. 加菊花:先在html里加个菊花图,然后在main.js里img.remove()
  2. 加骨架

3. 加占位内容,比如在html里写个加载中,文章标题 4. SSR技术。没必要。大炮打蚂蚁。

以不变应万变

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

有时候会有一些多余的用不到代码

有时候遇到特殊情况不知道怎么变通,比如没有html 的模块怎么做

太复杂的还是得用MVC,vue就不适合了

表驱动编程

  • 当你看到大批类似但不重复的代码
  • 眯起眼睛,看看到底哪些才是重要的数据
  • 把重要的数据做成哈希表,你的代码就简单了
  • 这是数据结构知识给我们的红利
  • 我们只要把填完整,vue已经把表外面的事情做好了

例如 表驱动编程:自动绑定事件

  1. 表指的是哈希表
  2. 表驱动编程可以减少重复代码,只讲重要的信息放在表里,然后利用表来编程
  3. 利用哈希表,把每一个的事件,元素,函数列出来
  4. 把每一个的函数定义出来
  5. 自动绑定事件:利用遍历哈希表,把元素绑定上事件。
//自动绑定事件
  //(1)把所有事件写成哈希表
  events: {
    "click #add1": "add",
    "click #minus1": "minus",
    "click #mul2": "mul",
    "click #divide2": "div"
  },
  //(2)每个事件要执行的函数写出来
  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 });
  },
  //(3)自动绑定事件
  autoBindEvents() {
    for (let key in c.events) {
      //对事件哈希表里每一个事件
      const value = c[c.events[key]]; //要执行的函数  如add
      const spaceIndex = key.indexOf(" ");
      const part1 = key.slice(0, spaceIndex); //事件名part1  如click
      const part2 = key.slice(spaceIndex + 1); //实际监听元素part2   如#add1元素
      v.el.on(part1, part2, value); //用事件委托,监听容器元素的part1事件,其实是监听他的子元素part2的part1事件,执行函数。
      //这里是用容器元素的子元素的id来选出子元素然后绑定事件给这些子元素,每次渲染子元素的id不会变的。所以用子元素id选出子元素在绑定事件,一劳永逸!
    }
  }
复制代码

事不过三

  1. 同样的代码写三遍,就应该抽成一个函数,如表驱动编程的自动绑定事件和下面的例子
class Model {
  constructor(options) {
    const keys = ['data', 'update', 'create', 'delete', 'get']
    keys.forEach((key) => {
      if (key in options) {
        this[key] = options[key]
      }
    })
  }
  update(){}
  create(){}
  delete(){}
  get(){}
  }
 //烂代码:超过三个,用遍历
class Model {
  constructor(options) {
  this.data = option.data
  this.update = option.update
  this.create = option.create
  this.delete = option.delete
  this.get = option.delete
  
//这里没有超过三个,就不用遍历,写成解构形式的对象最好  
class View {
    constructor ({el,html,render}){
        this.el = $(el)
        this.html = html
        this.render = render
    }
}
复制代码
  1. 同样的属性写三遍,就应该做成共用属性(原型或类) , 如MVC变成Model类、View类
  2. 同样的原型写三遍,就应该用继承:如EventBus
//在EventBus.js中
import $ from 'jquery'

class EventBus {
  constructor() {
    this._eventBus = $(window)
  }

  on(eventName, fn) {
    return this._eventBus.on(eventName, fn)
  }

  trigger(eventName, data) {
    return this._eventBus.trigger(eventName, data)
  }

  off(eventName, fn) {
    return this._eventBus.off(eventName, fn)
  }
}

export default EventBus

//在Model.js中
import EventBus from './EventBus'

class Model extends EventBus {   //让Model类继承EventBus类的所有属性
  constructor(options) { 
    super()                      //特别的。要这样把EventBus的constructor弄过来
    const keys = ['data', 'update', 'create', 'delete', 'get']
    keys.forEach((key) => {
      if (key in options) {
        this[key] = options[key]
      }
    })
  }

  create() {
    console && console.error && console.error('你还没有实现 create')
  }

  delete() {
    console && console.error && console.error('你还没有实现 delete')
  }

  update() {
    console && console.error && console.error('你还没有实现 update')
  }

  get() {
    console && console.error && console.error('你还没有实现 get')
  }
}


export default Model
复制代码
  • 代价:有的时候会造成继承层级太深,无法一下看懂代码。可以通过写文档、画类图解决

俯瞰全局

  • 把所有的对象看成点
  • 一个点和一个点怎么通信
  • 一个点和多个点怎么通信
  • 多个点和多个点怎么通信
  • 最终我们找出一个专用的点负责通信
  • 这个点就是eventBus (事件总线)
  • vue是EventTarget,有v.on() v.emit() v.off()
  1. 以前我们是
const eventBus = $(window)  //在全局
eventBus.trigger("m:updated");  //在数据增删改查函数中
eventBus.on("m:updated", () => {
      //④监听m:updated事件,每次触发就重新用新数据渲染一遍
      console.log("here");
      v.render(m.data.n);
    })
复制代码
  1. eventBus 提供了 on、off 和 trigger 等 API,on 用于监听事件,trigger 用域触发事件
  2. eventBus 主要用于对象间通信
  3. 使用 eventBus 可以满足最小知识原则,m 和 v 互相不知道对方的细节,
  4. 数据更新,就重新渲染一次。那怎么知道数据更新了?在数据更新函数加上触发eventBus的xxx事件,那么监听eventBus的xxx事件,要是触发了就重新用新数据渲染一次。
  5. 数据更新 → 触发eventBus的xxx事件 → 执行xxx事件监听函数:用新数据再次渲染
  • 现在 让所有类都继承了EventBus类,那每个类的具体的对象就可以直接使用.on .off .trigger 了

view = render(data)

  • 视图是对数据的渲染。每次数据改变就重新渲染一次
  • 比起操作DOM对象,直接render简单多了
  • 只要改变data,就可以得到对应的view
render(x) {
    if (v.el.children.length !== 0) v.el.empty(); //如果容器里有东西,就全删掉
    $(v.html.replace("{{n}}", x)).appendTo(v.el); //把html里的占位符替换成x,再加入容器中
  }
};
复制代码
  • vue不是这样,但是react是这样
  • 代价:render粗犷的渲染肯定比DOM操作浪费性能,还好我们后面会用到虚拟DOM ,虚拟DOM能让render只更新该更新的地方