MVC是一种设计模式,既是一种代码组织形式又是一种思想,他将系统分为三层:Model 数据,View 视图,Controller 控制器。
Model数据管理:专门处理数据,包括数据逻辑,数据请求,数据存储。它不对HTML,DOM,CSS以及视图逻辑进行操作,一般多数用于从服务器获取数据和保存数据。
View视图:即为用户可见区域,前端view主要负责HTML的渲染。
Controller控制器:主要负责view事件的逻辑处理,并更新Model,也监听View,总而言之Controller控制除视图和数据以外所有的事情。
下面我们通过一个原生JS例子来认识MVC这种设计模式。
这是一个留言板功能模块,我从leancloud注册了一个账号,作为数据库。然后在html中加入提交表单,绑定表单事件。输入信息提交后由js向leancloud服务器申请保存数据,保存成功后我们再从leancloud中拿到数据渲染到页面中,就这样简单的一个功能模块,我们试着用MVC的设计模式重构一遍。
重构前:
html
<script src="//cdn.jsdelivr.net/npm/leancloud-storage@3.14.0/dist/av-min.js"></script>
<section class="leave">
<h2>留言板</h2>
<ol id="messageList"></ol>
<div class="inputBox">
<form id="message">
<div>
昵称<input type="text" autocomplete="off" name="name">
</div>
<div>
内容<input type="text" autocomplete="off" name="content">
</div>
<input type="submit" name="提交" style="margin-top: 20px">
</form>
</div>
</section>
JS
//leancloud初始化,
var APP_ID = '6cdlCTFh4O4BU9GaTp79Fc4R-gLGzoHsz';
var APP_KEY = 'PrnMib9dmf7qc9B4vEEvOfm6';
AV.init({
appId: APP_ID,
appKey: APP_KEY
});
//从leancloud拿到数据
let query = new AV.Query('A_Tione')
query.find().then(e=>{
let array = e.map(item=> item.attributes)//拿到A_Tione的留言数据
console.log(array)
array.forEach((item)=>{
let li = document.createElement('li')
li.innerText = item.name+':'+item.content
messageList.appendChild(li)
})
})
//将表单数据保存到leancloud中
let myForm = document.querySelector('#message')
myForm.addEventListener('submit', function (e) {
e.preventDefault() //阻止默认事件
let name = myForm.querySelector('input[name=name]').value
let content = myForm.querySelector('input[name=content]').value
let Tione = AV.Object.extend('A_Tione');
let tione = new Tione();
tione.save({
'name': name,
'content': content
}).then(function(object) {
let li = document.createElement('li')
li.innerText = name+':'+content
messageList.appendChild(li)
myForm.querySelector('input[name=name]').value = ''
myForm.querySelector('input[name=content]').value = ''
console.log('保存成功')
})
})
我们可以看到代码是一坨坨的,需要用注释来区分 第一段是初始化,第二段是拿数据,第三段是保存数据。
现在代码少加上注释看起来还挺好理解的,可是代码行数上去了,哪怕加注释恐怕理解起来也不太容易,而且加太多注释同样耽误时间。于是乎经过程序员们不断地摸索,最后发现了一个办法,就是用MVC的思想,将代码分类整理,按照功能分为数据管理,视图和逻辑控制。
重构后:
第一步:
我们将html中包裹整个留言模块的DOM对象存入view变量中,这一步就是MVC中的V,view 视图。
第二步:
然后在model对象中定义leancloud初始化,获取数据和新增数据,注意model中只做数据相关的逻辑以及拿数据和保存数据,其它的逻辑统统交给controller完成。这步就是MVC中的M,model 数据管理。
第三步:
我们在controller对象中先定义好view: null, model: null, messageList: null,然后由controller中的init初始化从controller外面拿view,model,messageList。将他们三个的值保存到controller内部作用域中,这样的好处就是所有要用到的东西我们都能在controller中找到,不必再从外面调取,而且需要传进来的值已经定义好放在那了,你只需要按部就班地将需要的值传给定义好的空属性即可。
然后调用接下来的方法函数,进行视图相关的逻辑操作。例如在获取数据时调用model里面的this.model.fetch()方法,得到一个promise对象然后接zhen()继续后面的逻辑操作。这样数据处理model与视图逻辑处理controller就解耦了,各自处理各自的事儿,不用像原来的代码全部混在一坨,需要看完所有的代码才能理解写的什么。最后这步就是MVC中的C,controller 控制器。
理解了这个例子,你就会发现后面写代码都是一个套路,view相关的html,model数据处理,controller定义好需要用到的属性初始化,然后将视图相关的逻辑全部放在controller里面,最后controller.init(view, model)一下将view和model传入controller即可。
好处:
- 按MVC思想将代码划分好后,就不用费力写注释,因为一眼就知道代码在干嘛。
- 增强代码阅读性
- 省事,按照MVC模板一套,改改controller的视图处理逻辑、model的数据请求与相应,一个模块就写完了。
!function () {
let view = document.querySelector('section.leave')
let model = {
init: function () {
let APP_ID = '6cdlCTFh4O4BU9GaTp79Fc4R-gLGzoHsz';
let APP_KEY = 'PrnMib9dmf7qc9B4vEEvOfm6';
AV.init({appId: APP_ID, appKey: APP_KEY});
},
fetch: function () {//获取数据
let query = new AV.Query('A_Tione')
return query.find() //返回一个Promise对象
},
save: function (name, content) {
let Tione = AV.Object.extend('A_Tione');
let tione = new Tione();
if (name && content) {
return tione.save({//返回一个Promise对象
'name': name,
'content': content
})
}
}
}
let controller = {
view: null,
model: null,
messageList: null,
init: function (view, model) {
this.view = view
this.model = model
this.messageList = view.querySelector('#messageList')
this.form = view.querySelector('form')
this.model.init()
this.loadMessages()
this.bindEvents()
},
loadMessages: function () {
this.model.fetch().then(e => {
let array = e.map(item => item.attributes)//拿到A_Tione的留言数据
array.forEach((item) => {
let li = document.createElement('li')
li.innerText = item.name + ':' + item.content
this.messageList.appendChild(li)
})
})
},
bindEvents: function () {
this.form.addEventListener('submit', (e) => {
e.preventDefault() //阻止默认事件
this.saveMessage()
})
},
saveMessage: function () {
let myForm = this.form
let name = myForm.querySelector('input[name=name]').value
let content = myForm.querySelector('input[name=content]').value
this.model.save(name, content).then(object => {
let li = document.createElement('li')
li.innerText = name + ':' + content
this.messageList.appendChild(li)
myForm.querySelector('input[name=name]').value = ''
myForm.querySelector('input[name=content]').value = ''
console.log('保存成功')
})
},
}
controller.init(view, model)
}.call()