MVC 模式
概述
MVC 模式的目的是实现一种动态的程式设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式透过对复杂度的简化,使程序结构更加直观。软件系统透过对自身基本部分分离的同时也赋予了各个基本部分应有的功能:
- 控制器(Controller)- 负责转发请求,对请求进行处理。
- 视图(View) - 界面设计人员进行图形界面设计。
- 模型(Model) - 程序员编写程序应有的功能(实现算法等等)。
前端的 MVC
-
前端的 View 是,与页面上元素直接相关的部分都属于 View。包括 html,css 和一部分直接控制页面元素的 JS。作为观察者模式中的观察者,它可以从 Model 中得到数据,并将其显示到页面上。而关于数据的变更和请求,则统统交给 Controller 处理。
-
Controller 作为 Model 和 View 的粘合剂,Controller 将 View 方面的请求转发给合适的 Model,在必要的时候也会去更新 View。而 Controller 本身也可以作为 Model 的观察者,获取 Model 的变更。而作为 Controller 本身,就不应该有涉及到页面元素的代码了。
-
与后端的沟通、AJAX 请求以及对数据的处理都属于 Model 的工作。Model 本身不知道谁是 View,谁是 Controller。它只提供一些方法供 View 和 Controller 调用,并且将变更通知给它的观察者 View 或 Controller。显然,Model 与页面元素之间也解耦了。
<body>
<div class="page">
<section id="app1"></section>
<section id="app2"></section>
</div>
<script src="main.js"></script>
</body>
import './app1.css'
import $ from 'jquery'
const eventBus = $(window)
// 数据相关都放到m
const m = {
data: {
n: parseInt(localStorage.getItem('n'))
},
update(data) {
Object.assign(m.data, data)
eventBus.trigger('m:updated')
localStorage.setItem('n', m.data.n)
},
}
// 视图相关都放到v
const v = {
el: null,
html: `
<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>`,
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', () => {
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
以上代码,将数据相关代码都放到了 m 对象中,m 负责获取数据,并通过 EventBus 在事件更新后,触发监听;
对象 v 负责获取 html 元素,并负责初始化和更新触发后重新渲染 dom;
对象 c 中保存了保存了相关的方法和事件,在事件更新数据后,告诉对象 m,数据更新,保存最新的数据,并等待监听,监听触发后,通知 view 重新渲染 dom。
事件总线(eventBus)
在老式编程模型中,每一位代码都会与其他每一位代码进行对话。如果更改了页面上的元素或存储在对象中的数据,它将立即告知该页面或对象上的其他所有人该更改。这里的中心问题是可伸缩性–为了使事情保持同步,您的每一段代码都需要了解其他每一段代码。
对于一个简单的 Hello World 应用程序,没什么大不了的。但是,对于较大的程序,这将成为繁琐的噩梦。
一个事件总线缓解这个问题,并允许你写两个完全分开的代码。一段代码将通过事件总线预订事件,而不是查看其邻居以查看更改 。当另一段代码(再次通过事件总线)触发事件时,将通知每个观察者。
jQuery 提供了相关的事件方法 API,可以实现 eventBus,
const eventBus = $(window);
//通过jQuery的on方法添加一个事件
eventBus.on('m:updated', () => {
v.render(m.data.n)
});
//通过jQuery的trigger方法触发一个事件
eventBus.trigger('m:updated');
//通过jQuery的off方法移除一个事件
eventBus.off("m:updated")
-
on() 方法在被选元素及子元素上添加一个或多个事件处理程序。
$(selector).on(event,childSelector,data,function)
event:必需。规定要从被选元素移除的一个或多个事件或命名空间。由空格分隔多个事件值,也可以是数组。必须是有效的事件。
childSelector:可选。规定只能添加到指定的子元素上的事件处理程序。
data:可选。规定传递到函数的额外数据。 function:可选。规定当事件发生时运行的函数。 -
trigger() 方法触发被选元素上指定的事件以及事件的默认行为(比如表单提交)。
$(selector).trigger(event,eventObj,param1,param2,...)
event:必需。规定指定元素上要触发的事件。 可以是自定义事件,或者任何标准事件。
param1,param2,... 可选。传递到事件处理程序的额外参数。 -
off() 方法通常用于移除通过 on() 方法添加的事件处理程序。
$(selector).off(event,selector,function(eventObj),map)
event:必需。规定要从被选元素移除的一个或多个事件或命名空间。由空格分隔多个事件值。必须是有效的事件。
selector:可选。规定添加事件处理程序时最初传递给 on() 方法的选择器。 function(eventObj):可选。规定当事件发生时运行的函数。
map:规定事件映射 ({event:function, event:function, ...}),包含要添加到元素的一个或多个事件,以及当事件发生时运行的函数。
jQuery 还提供了很多的其他事件方法,例如 one(),ready(),submit(),load(),这些方法就不一一赘述了,如需用时,可以自行查阅 jQuery 文档。