浅析MVC
一、MVC是什么?
MVC(Model View Controller)是一种架构设计模式.
M : model
,即数据层(数据模型),负责操作所有的数据。
V :view
,即视图层,负责所有UI界面,是提供给用户的操作界面,是程序的外壳。
C :Controller
,即控制层,负责根据用户从“视图层”输入指令,选取“数据层中的数据”,对其进行相应的操作(绑定事件等),产生最终结果。
这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。
关于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) } }
>> MVC 实例
我们的目标是做一个加减乘除计算器
每次点击加、减、乘、除按钮,数值就会变,基本思想就是监听click事件
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
在外部引用时就直接
import x from './app1.js' x.init('#app1') 把容器div传进去 然后在html里写一个<div id="app1"></div>作为容器即可
二、EventBus
>>EventBus是什么?
EventBus主要用于对象之间的通信,比如在上面的例子中,Model 数据模型 和View 视图模型彼此不知道彼此的存在,但是又需要通信,于是就要用到EventBus
总结:使用 eventBus 可以满足最小知识原则,m 和 v 互相不知道对方的细节,但是却可以调用对方的功能
>>EventBus 常用API
eventBus 提供了 on、off 和 trigger 等 API,on 用于监听事件,trigger 用于触发事件
比如在上面的MVC模型中, M数据模型更新时,会 trigger 触发一个事件
EventBus.on()
监听事件
EventBus.trigger()
触发事件
EventBus.on()
取消监听事件
eventBus.trigger('m:updated') //触发事件 eventBus.on('m:updated',()=>{ //监听事件 v.render(m.data.n) })
三、表驱动程序
随着逻辑复杂性的增加,if/else 或switch中的代码将变得越来越肿,代码可读性不强。所谓的表驱动程序,指的是把一大堆的if...else这样的逻辑处理,转化成从一个hash表中查找key--value对的过程。
例如如果要一个可以返回每个月中天数的函数(为简单起见不考虑闰年),用if else:
function iGetMonthDays(iMonth) { let iDays; if(iMonth === 1) {iDays = 31;} else if(iMonth === 2) {iDays = 28;} else if(iMonth === 3) {iDays = 31;} else if(iMonth === 4) {iDays = 30;} else if(iMonth === 5) {iDays = 31;} else if(iMonth === 6) {iDays = 30;} else if(iMonth === 7) {iDays = 31;} else if(iMonth === 8) {iDays = 31;} else if(iMonth === 9) {iDays = 30;} else if(iMonth === 10) {iDays = 31;} else if(iMonth === 11) {iDays = 30;} else if(iMonth === 12) {iDays = 31;} return iDays; }
用表驱动程序(包括闰年判断):
const monthDays = [ [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ] function getMonthDays(month, year) { let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0 return monthDays[isLeapYear][(month - 1)]; } console.log(getMonthDays(2, 2000))
从上面的代码就可体现出用表驱动程序有以下优点:
- 减少了大量重复代码
- 提高了代码的可读性
四、关于模块化
MDN文档:JavaScript modules 模块
>>模块化
这里的模块化值得是前端模块化,模块化是MVC的重要前提。模块化就是把相对独立的代码从一大段代码里抽取成一个个小模块每个模块之间相对独立,方便日后的维护。
ES6 的语法里引入了 Import 和 export 就是用来实现模块化。
export default c; // 默认导出 export { c }; // 另外一种导出方式。记得要加花括号
>>最小知识原则
以最少的知识使用一个模块,平时我们需要在 html 里引入 css 和 js,现在只需要引入一个模块 js 就够了。
>>M+V+C
每个模块都可以用M+V+C
的模式搞定,即使MVC在vue
里浓缩成了一个 V。
>>事不过三(抽象思维)
把重复的事情抽象简单
- 重复的代码==>抽象成函数
- 同样的属性==>抽象成原型或类
- 同样的原型==>使用继承