MVC
MVC的解释千千万,唯一统一的认识就是MCV分别指model、view、controller,至于其它的咱也不知道对错。
我理解的MVC,就像行为与样式分离,是把业务按照M、V、C的功能进行解耦:
- Model用来管理业务逻辑相关的数据以及对数据的处理方法
- View=render(data)用来展示Model里数据的当下状态,并实时跟随Model更新
- Controller负责接受并响应View上用户的交互行为以及对Model内部数据进行操作
刚学编程时,为了完成一个功能,我们想到什么写什么,只要最后能凑出来就成。这种代码常称为“意大利面条”代码,就是一坨*的意思,而且面条之间没有章法可循,你不知道它从哪来,也不知道它到哪去,一旦项目大起来,改代码的工作量比从头写的工作量还大,事情朝着不可控的方向发展了去,改代码的人就很惨。
于是,理清业务逻辑,形成代码规范,方便日后维护,就显得异常重要,so前端开始借鉴后端的架构设计模式,实现了各种MV*框架。
这里以一个实现+1功能的小例子来演示下:
step1
入门级代码
step2
M、V、C初步分离,这块代码不能单独运行,看看MVC的意思就行
import './app.css'
import $ from 'jquery'
// 数据相关都放到m
const m = {
data: {
n: parseInt(localStorage.getItem('n'))
}
}
// 视图相关都放到v
const v = {
el: null,
html: `
<div id="add">{{n}}</div>
<button id="btn">+1</button>
`,
render(n) {
$(v.html.replace('{{n}}', n))
.appendTo($('body>#app'))
}
}
// 其他都放c
const c = {
init() {
c.ui = {
$number: $('add'),
$btn: $('btn')
}
c.bindEvents()
},
bindEvents(){
c.ui.$btn.on('click', ()=>{
let n = m.data.n
n+=1
localStorage.setItem('n', n)
c.ui.$number.text(n)
})
}
}
v.render()
c.init()
V和C其实还可以进一步合并,这里就跳过了~
step3
之后,你又觉得M、V好像可以再封装一下,搞个类什么的,把一样的东西都塞进去,事件监听什么的所有地方都会用,让所有类继承吧,也方便了下次用。
import "./app.css"
import $ from "jquery"
import Model from "./base/Model.js"
import View from "./base/View"
const m = new Model({
data: {
//初始化数据
n: parseFloat(localStorage.getItem('n')) || 100,
},
update: function(data){
Object.assign(m.data, data)
m.trigger('m:updated')
localStorage.setItem('n', m.data.n.toString())
}
})
const init = (el)=>{
new View({
el: el,
data: m.data,
// 初始化html
html: `
<div class="output">
<span id="number">{{n}}</span>
</div>
<div class="actions">
<button id="add">+1</button>
</div>`,
render(data){
if(this.el.children.length !== 0) this.el.empty()
$(this.html.replace('{{n}}', data.n)).appendTo(this.el)
},
events: {
'click #add': 'add',
},
add(){
m.update({n: m.data.n + 1})
},
})
}
export default init
这里是不是已经和Vue的样子有那么点像了?创建一个实例,再在里面填上你要的data、method啥的,还有render、template啥的,只不过Vue里看上去你只用new一个实例就行,还有单文件模板啥的,一上来你只用填自己要写的东西,脏活累活全被Vue干了,总之很方便。
是不是感觉这不是咱平常学的JS了?刚接触Vue的时候我也这么想,感觉自己JS学了跟白学似的。那是因为我们不知道,意大利面代码=>MVC模式=>框架这中间的过程,多去看看相关的教程讲解,一步步跟着来,你就懂了~
MVC绝不仅是一道与MVVM、MVP绑一块,解释下每个层的关系和事件流的面试题,而是深入了解框架的一块敲门砖。
啥?你问我10行代码能搞定的事,为什么要写这么多乱七八糟看不懂的东西?来来来,甲方爸爸再给你加几个功能需求试试?复制粘贴到你哭,这时候你是不是又要自嘲自己是搬砖工了?
表驱动编程
说到代码重复,就要说说表驱动编程。表驱动编程就像封装函数,函数参数(包括)与存在映射关系的对象一一对应,来减少大量重复代码。
再拿上面的例子来说,加上减、乘、除的功能:
events: {
'click #add': 'add',
'click #sub': 'sub',
'click #mul': 'mul',
'click #div': 'div',
},
add(){
m.update({n: m.data.n + 1})
},
sub(){
m.update({n: m.data.n - 1})
},
mul(){
m.update({n: m.data.n * 2})
},
div(){
m.update({n: m.data.n / 2})
}
再加上下面这波这样的操作,简直不要太爽,以后再加新的类似功能,你只用加4行代码就完事啦!是不是很爽~这样一来,就再也不用写一堆重复无用的烂代码了
for(let item in this.events) {
const operate = this[this.events[item]]
const spaceIndex = item.indexOf(' ')
const eventName = item.slice(0, spaceIndex)
const element = item.slice(spaceIndex+1)
this.el.on(eventName, element, operate)
}
EventBus
上面的例子可以看到,View既要从M中取数据,还会根据用户操作来改变M中的数据,这中间少不了两个层之间的交互,以及监听,这就要EventBus来镇场子了。如何监听data的改变的呢?
Model:m.trigger('m:updated')
View:m.update({n: m.data.n + 1})
当data改变时,会调用m.update()
方法,此方法里的m.tigger('m:updated')
会有一个专门的机制来监听它,就是下面的this.on
,监听到m:updated
就立即从新渲染页面那一块的数据。
this.on('m:updated', ()=>{
this.render(this.data)
})
在层层封装之下,底层都离不开EventBus的事件监听,因为数据是活的,会被传递。这里我们可以自己造一个事件,来满足不同条件下的监听需求。然后通过继承,让每个实例都有监听功能。
在jQuery中,有$on
,$off
,$trigger()
来实现监听、解除监听、触发;
在Vue中,则是$on
,$off
,$emit
来实现此功能。