MVC 是什么

架构模式的一种,MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
这个模式认为,程序不论简单或复杂,从结构上看,都可以分成三层(视图层,数据层,控制层)。这三层紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。

模块化

在项目中实现「模块化」,就是将一个 js 文件中的代码按功能分类为多个 js 文件,将功能相同的代码放到一个文件中。

随着应用的功能不断增加,业务逻辑越来越复杂,代码也会变得更加复杂。如果仍将所有功能代码放在一个 js 文件中,不同功能的代码散乱一团难以查找辨别,可能起变量名都会变得非常费劲,最终导致代码的可读性、复用性极差,后期难以维护。所以,为了保证「代码能有清晰的结构」、「方便查找某个功能对应的代码区」,我们依据功能不同,将代码拆分成不同的模块(文件),使各个模块之间实现「解耦」。

  • 解耦:每个模块的代码都独立存在,不需要依赖其他模块。(甚至每个模块使用不同的框架。只不过体积会大一点)
  • 就像我们玩的积木一样,各个积木可以组合在一起形成一个形状,又可以拆分,又可以替换,因为各个积木块都是独立的,只要他们之间的接口(形状)匹配,就可以灵活地组合在一起,解耦就是为了逐渐达到这种理想的状态。

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

  • 高内聚,是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
  • 低耦合,是指模块之间的联系越少越好,接口越简单越好,实现低耦合,细线通信。
  • 如果各个模块之间接口很复杂,说明功能划分有不合理之处、模块之间的耦合太高,同时也说明单个模块的内聚不高。

    MVC 伪代码

    ```javascript // 数据层,关于数据的操作放在这里 const m = { data: { // 数据初始化 n: parseInt(localStorage.getItem(‘number’) || 100)
    }, update: function (data) { /更新数据/ }, delete: function (data) { /删除数据/ }, get: function (data) { /获得数据/ } }

// 视图层,关于视图的操作放在这里 const v = { el: ‘容器’, html: ‘需要插入元素内的HTML内容’, render(data){ /(获取数据)渲染html视图/ } }

// 控制层,关于事件监听的放到这里 const c = { // 找到重要的元素绑定事件 // 如果触发事件调用更改数据方法及渲染方法 a: $(‘找到a’), b: $(‘找到b’), c: $(‘找到c’), bindEvents: function(){ // bindEvents 在 render 时执行 a.on(‘click’, function(){ // 调用数据层方法更改数据 // 调用视图层方法渲染页面 }) b.on(‘click’, function(){//}) b.on(‘click’, function(){//}) } }

  1. <a name="dKT0M"></a>
  2. # 表驱动编程
  3. 表驱动编程(Table-Driven Methods)是一种编程模式。<br />作用是**消除代码中频繁的 if else 或 switch case 的逻辑结构代码**,使代码更加简化
  4. - 事实上,任何信息都可以通过表来挑选。在简单情况下用逻辑语句是更简单的,但是一旦判断条件增多,那可能要写大量重复的判断语句,这时候我们通过**遍历**表来实现条件判断,将事半功倍。
  5. 常规写法:看起来逻辑直白,但但随着功能复杂度的增加,代码量会线性增加
  6. ```javascript
  7. add1(){ ... }
  8. min1(){ ... }
  9. mul2(){ ... }
  10. div2(){ ... }
  11. document.querySelector('#add1').addEventListener('click', add1)
  12. document.querySelector('#min1').addEventListener('click', min1)
  13. document.querySelector('#mul2').addEventListener('click', mul2)
  14. document.querySelector('#div2').addEventListener('click', div2)

表驱动写法:让代码具有一个稳定的复杂度,不论功能复杂度高低,代码量基本稳定。

  1. const controller = {
  2. add1(){ },
  3. min1(){ },
  4. mul2(){ },
  5. div2(){ },
  6. events: { // 表驱动编程(对象)
  7. "click #add1": "add1", // key 的前半为要监听的事件,后半为监听的元素,value 为要执行的方法
  8. "click #min1": "min1",
  9. "click #mul2": "mul2",
  10. "click #div2": "div2"
  11. },
  12. autoBindEvents() {
  13. for(let key in this.events){ // 遍历对象获得对应的 key 去做赋值操作
  14. const handler = this[this.events[key]]
  15. const [event, selector] = key.split(" ") // ["click", "#min1"]
  16. $("容器").on(event, selector, handler) // 将提取出来的值去监听事件
  17. })
  18. }
  19. }

eventBus(事件总线)

作用是实现各个模块之间的通信(一对一,一对多,多对多)。

伪代码

  1. const eventBus = $(window) //使用jquery实现
  2. updata(){
  3. //更新数据
  4. eventBus.trigger('updata' //触发updata事件
  5. }
  6. eventBus.on('updata',()=>{
  7. //监听到数据更新,执行渲染函数
  8. })

当多个应用功能都须用到 eventBus 时,将 eventBus 单独写成一个类 EventBus.js,让类继承 EventBus,这样创建的每个实例都拥有了 eventBus 上的方法。

  1. //EventBus.js
  2. class EventBus{
  3. constructor(){
  4. this._eventBus =$(window)
  5. }
  6. on(eventName,fn){
  7. return this._eventBus.on(eventName,fn)
  8. }
  9. trigger(eventName,data){
  10. return this._eventBus.trigger(eventName,data)
  11. }
  12. off(eventName,fn){
  13. return this._eventBus.off(eventName,fn)
  14. }
  15. }
  16. export default EventBus //导出
  1. //view.js
  2. import EventBus from './EventBus.js'
  3. class View extends EventBus{
  4. constructor(options) {
  5. super()
  6. Object.assign(this, options);
  7. this.on("m:updatad", () => {
  8. this.render(this.data); //可直接调用 eventBus 上的方法
  9. });
  10. }
  11. }
  12. export default View;
  1. // app.js
  2. import Model from "./base/Model.js"
  3. const m = new Model()
  4. m.trigger('updata') //调用原型上继承的方法