浅析 MVC

166 阅读6分钟

简介

MVC指的是模型-视图-控制器(model-view-Controller),是一种软件设计模式,用于将应用程序分离为三个主要的组成部分:模型、视图和控制器。

  • 模型(M):处理应用程序的数据和业务逻辑,通常是应用程序中的数据访问层。
  • 视图(V):负责应用程序的用户界面,例如窗体、页面、控件等。
  • 控制器(C):处理用户输入和向视图和模型对象发送命令的逻辑,例如点击按钮、选择下拉列表等。

在MVC模式中,模型、视图和控制器分别独立存在,使得系统更加灵活,并且易于开发和维护。通过MVC模式,可以有效地分离业务逻辑和用户界面,降低了系统的耦合性,提高了应用程序的可维护性和可扩展性。

示例

分别用伪代码表示三个部分的工作内容

  • Model 数据模型
        let Model={
            data:{数据源},
            create:{增加数据},
            delete:{删除数据},
            update(data){
                Object.assign(m.data,data)//用新数据替换旧数据
                eventBus.trigger('m:update')//eventBus触发'm:update'信息,通知View刷新界面
            },
            get:{获取数据}
        }
  • View 视图层
        let View={
            el:要刷新的元素,
            html:'要显示在页面上的刷新内容'
            init(){
                v.el:初始化需要刷新的元素
            },
            render(){
                刷新页面
            }
        }
  • Controller 控制器
    let Controller={
        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)
        }
    }

代码实例: coolkechang/MVC-demo-2 (github.com)

EventBus

  • EventBus 是一种观察者模式的实现,它是一种事件发布/订阅机制,用于简化组件之间的通信。
  • 在EventBus中,组件通过订阅事件的方式来接收事件消息,而不是通过某些主动通知的方式来实现。当一个事件被发布时,所有订阅了该事件的组件都会收到该事件消息,然后可以进行相应的处理。
  • EventBus的主要作用是解耦组件之间的关系,使得它们更加独立和可重用。也就是说使用 eventBus 可以满足最小知识原则,例如 m 和 v 互相不知道对方的细节,但是却可以调用对方的功能。

常用 API

  • eventBus.on(eventName, handlerFunction): 注册一个事件处理器来监听某个事件。
  • eventBus.emit(eventName, eventArgs): 触发一个事件,并传递一个可选参数作为事件的数据。
  • eventBus.off(eventName, handlerFunction): 移除一个事件处理器,使其不再监听指定的事件。
  • eventBus.once(eventName, handlerFunction): 注册一个只执行一次的事件处理器,该处理器在第一次事件发生时被调用,然后就被从监听器列表中删除。
  • eventBus.removeAllListeners(eventName): 移除指定事件的所有处理器,使其不再被调用
  • eventBus.listeners(eventName): 返回一个数组,其中包含所有监听指定事件的处理器函数。

例如:

// 注册事件处理器
eventBus.on('message', function(message) {
console.log('Received message:', message);
});

// 触发事件并传递数据
eventBus.emit('message', 'Hello, world!');

表驱动编程

  • 表驱动编程是一种编程方法,通过使用表格或映射表来支持一组相关操作。这些表格通常包含预计算的数据和相应的代码逻辑,以便程序可以直接使用它们而不需要大量的控制流和条件语句。
  • 在表驱动编程中,表格可以被看作是一种对代码的某种形式的压缩,避免编写大量的 if/else 或 switch/case 语句的重复模式。
  • 表驱动编程适用于需要处理许多不同的情况或对输入数据进行过滤和转换的情形。这种编程方法可以使代码更简洁、可读性更高、容易维护,因为它们可以降低代码量、减少代码重复和简化代码结构。此外,表驱动编程还可以支持动态更新和实时变更所需的数据和代码,而无需重启应用程序。
  • 常见的使用表驱动编程的例子包括词法分析器、语法分析器、编译器、Lua框架等。在这些场景中,表驱动编程可以显著提高程序的性能和可维护性。

例如:

实现一个数字转中文的程序,将输入的一组数字转换为中文汉字。

1 ->2 ->3 ->4 ->

使用 if / else

function numberToChinese(numList) {
  let result = '';
  for (let i = 0; i < numList.length; i++) {
    const num = numList[i];
    if (num === 1) {
      result += '一';
    } else if (num === 2) {
      result += '二';
    } else if (num === 3) {
      result += '三';
    } else if (num === 4) {
      result += '四';
    }
  }
  return result;
}

改用表驱动编程方法;

const NUMBER_MAP = {
  1: '一',
  2: '二',
  3: '三',
  4: '四'
};

function numberToChinese(numList) {
  const result = [];
  numList.forEach(num => {
    const chinese = NUMBER_MAP[num];
    if (chinese) {
      result.push(chinese);
    }
  });
  return result.join('');
}

在这个例子中,我们创建了一个常量 NUMBER_MAP,将数字和对应的中文汉字一一映射。然后编写了一个 numberToChinese 函数,该函数接收一个数字列表作为输入,并使用 forEach 遍历输入列表,将数字通过 NUMBER_MAP 映射表转换为中文汉字。最后,结果将拼接起来,并作为函数的返回值。

使用表驱动编程的优点在于,我们可以将不同的转换规则和映射表放在单独的文件或对象中,这样可以方便地修改和维护代码。当要添加新的转换规则时,我们只需要简单地修改映射表,而不需要更改转换逻辑。这种简单而优雅的代码风格可以使代码更易于阅读和维护。

模块化

模块化是一种将程序代码拆分成独立和可重用组件的方法,从而使得程序结构更加清晰、代码更加可维护。在模块化的程序设计中,每个组件都是一个独立的模块,其内部实现和数据不会被外部代码所干扰。这样可以使得代码的复用性和可维护性得到显著提高。

一个良好的模块化程序设计应该具备以下特点:

  • 高内聚性:每个模块应该封装一组具有高度内聚性的函数或对象。
  • 低耦合性:模块之间应该通过接口进行通信,而不是直接依赖和调用彼此的函数或对象。
  • 可拓展性:模块应该能够方便地扩展和修改,而不需要影响其他模块的实现。
  • 可重用性:模块应该能够在不同的项目或场景中复用,从而提高代码的复用性和开发效率。

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')

关于模块化:JavaScript 模块 - JavaScript | MDN (mozilla.org)


资料来源:饥人谷前端课程