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)
}
}
EventBus 有哪些 API,是做什么用的
前面提到MVC三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。当层与层之间需要通信时,这时就需要用到EventBus。EventBus主要用于组件之间的监听与通信。
eventBus.trigger('m:updated') //触发事件
eventBus.on('m:updated',()=>{ //监听事件
v.render(m.data.n)
})
eventBus.off('m:updated')//取消监听
表驱动编程是做什么的
为什么会出现?
随着逻辑复杂性的增加,if/else 或switch中的代码将变得越来越肿,代码可读性不强。所谓的表驱动程序,指的是把一大堆的if...else这样的逻辑处理,转化成从一个hash表中查找key--value对的过程。
隐含在背后的思想:
很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:
-
控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
-
隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。
-
机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。
数据驱动编程可以用来做什么:
它可以应用在函数级的设计中。
同时,它也可以应用在程序级的设计中,典型的比如用表驱动法实现一个状态机(后面写篇文章专门介绍)。
也可以用在系统级的设计中,比如DSL(这方面我经验有些欠缺,目前不是非常确定)。
我是如何理解模块化的
这里的模块化值得是前端模块化,模块化是MVC的重要前提。模块化就是把相对独立的代码从一大段代码里抽取成一个个小模块每个模块之间相对独立,方便日后的维护。
ES6 的语法里引入了 Import 和 export 就是用来实现模块化。
export default c; // 默认导出
export { c }; // 另外一种导出方式。记得要加花括号
在项目中实现「模块化」,通俗来讲,就是创建多个 js 文件,把相关功能的代码聚集到同一个 js 文件中,这样就实现了模块化。
所以,为了保证「代码能有清晰的结构」,「方便查找某个功能对应的代码区」,我们依据功能不同,将代码拆分成不同的模块(文件),使各个模块之间实现「解耦」。
解耦:每个模块的代码都独立存在,不需要依赖其他模块。(甚至一个模块用 Vue、一个用 React、一个用 jQuery 都没问题。只不过体积会大一点)
就像我们玩的积木一样,各个积木可以组合在一起形成一个形状,又可以拆分,又可以替换,因为各个积木块都是独立的,只要他们之间的接口(形状)匹配,就可以灵活地组合在一起,解耦就是为了逐渐达到这种理想的状态。
划分模块的一个准则是「高内聚、低耦合」
高内聚,是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
低耦合,是指模块之间的联系越少越好,接口越简单越好,实现低耦合,细线通信。
如果各个模块之间接口很复杂,说明功能划分有不合理之处、模块之间的耦合太高,同时也说明单个模块的内聚不高。
引入class优化
• 同样的代码写三遍就应该抽成函数
• 同样的属性写三遍就应该做成公用属性(原型或class)
• 同样的原型写三遍就应该用继承
class Model{
constructor(options){
['data','update','create','delete','get'].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')
}
}