MVC是什么?
MVC是一种架构模式,它将应用抽象为3个部分:模型(Model)、视图(View)、控制器(Controller)。
一个事件发生的过程(通信单向流动)
1、用户在视图V上与应用程序交互
2、控制器C触发相应的事件,要求模型M改变状态(读写数据)
3、模型M将数据发送到视图V,更新数据,展现给用户。
在JS的传统开发模式中,大多基于事件驱动的:
1、hash驱动
2、DOM事件用来驱动视图
3、模型事件用来驱动模型和模型结合
所以JS中的MVC的特点是:单向流动、事件驱动
一、模型
模型存放着应用的所有数据对象,比如例子中的store模型,存放着每一条记录与之有关的逻辑。模型有对数据直接访问的权利,不依赖于视图和控制器,不关心页面如何显示及如何被操作。
数据是面向对象的,当控制器要求模型读写数据时,模型就将数据包装成模型实例,任何定义在这个数据模型上的函数或逻辑都可以直接被调用。
模型不关心不包含视图和控制器的逻辑,它们应该是相互解耦的,模型与视图的耦合显然是违反MVC架构原则,但往往我们有时候因为业务关系而无法完全解耦。
模型表现了领域特定的数据,当一个模型有所改变的时候,它会通知它的观察者。
二、视图
视图是呈现给用户的,是用户交互的第一入口,他定义配置管理者每个页面相应的模板与组件,表现为一个模型的当前状态。视图通过观察者模式监听模型层上的数据改变以获得最新的数据,来呈现最新的页面,所以页面首次加载时往往是从接收模型的数据开始。实时的更新HTML页面,包括一些事件的注册或者ajax请求操作都是放在视图层来完成。
三、控制器
控制器是模型和视图之间的桥梁,集中式的配置和管理事件分发、模型分发、视图分发、还用来权限控制、异常处理等。我们的应用中往往是有多个控制器的。
页面加载完成后,控制器会监听视图的用户交互,一旦用户发生交互时,控制其作出对视图的选择,触发控制器的事件处理机制,去派发新的事件,通知模型更新数据。
控制器接受用户的操作,最主要的是视图层的事件,然后调用模型或视图去完成用户的操作。比如当页面上触发一个事件,控制器不输出任何东西及对页面做任何处理,他只是接受请求并决定调用模型中的哪个方法去处理请求,然后在调用哪个视图中的方法来显示返回的数据。
ex:todoList
用原生js写的todoList,点击输入文字点击确定就添加,删除是直接点击该行信息。
思路:
1、V层定义配置了一个显示数据的字符串模板,同时定义一个订阅者的回调函数render()用于页面更新数据。
2、C层监听用户的添加与删除操作,添加是add()函数,它执行了回调函数render,同时向M层写入数据,通知M层改变,删除操作同理。
3、M层是本地存储localStorage。模拟一个存储数据对象的后台模型。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<header>
<h3>待定事项</h3>
</header>
<main>
<ul id="todoList"></ul>
<input type="text" id="content" name="">
<button id="confirm">确认</button>
</main>
<script type="text/javascript">
(function(){
const ADD_KEY='_todoList_'
const Utils={
//模拟Model(实体模型)
store(key,data){
if(arguments.length>1){
return localStorage.setItem(key,JSON.stringify)
}else{
let storeData=localStorage.getItem(key);
return (storeData && JSON.parse(storeData))||[];
}
}
}
class Todo{
constructor(id,text=""){
this.id=id;
this.text=text;
}
}
let App={
init(){
this.todos=Utils.store(ADD_KEY)
this.findDom()
this.bindEvent()
this.render()
},
findDom(){
this.contextBox=document.querySelector("#content");
this.confirm=document.querySelector("#confirm");
this.todoList=document.querySelector("#todoList");
this.todoListItem=document.getElementsByTagName("li")
},
//模拟Controller(业务逻辑层)
bindEvent(){
this.confirm.addEventListener('click',()=>{
//要求模型M改变状态,add()函数是写入数据操作
this.add()
},false)
},
//抽象成一个视图
view(){
let fragment=document.createDocumentFragment();
fragment="";
for(let i=0;i<this.todos.length;i++){
fragment+='<li>${this.todos[i].text}</li>'
}
this.todoList.innerHTML=fragment;
},
render(){
this.view()
Utils.store(ADD_KEY,this.todos)
},
getItemIndex(item){
let itemIndex;
if(item.target.tagName.toLowerCase()==='li'){
let arr=Array.prototype.slice.call(this.todoListItem);
let index=arr.indexOf(item.target);
return itemIndex=index;
}
},
add(e){
let id=Number(new Date());
let next=this.contentBox.value();
let addTodo=new Todo(id ,text);
this.todos.unshift(addTodo);
this.render();
},
remove(item){
let index=this.getItemIndex(item);
this.todos.splice(index,1);
this.render();
}
}
App.init();
})()
</script>
</body>
</html>
MVC槟城把所有精力放在数据处理,尽可能减少对网页元素的处理。对于有一定数量功能的网页,MVC模式下强制规范代码简化减少重复代码,使代码易于补充。
MVC模式的弊端:
1)控制层和视图层耦合,导致没有真正分离和重用
2)在同一业务逻辑下,如果存在多种视图呈现,需要视图定义配置多个模板引擎、数据解析、多次处理数据与页面更新。代码就充满了各种选择器与事件回调。