设计模式
在讲述MVC之前,我们先来了解下什么是“设计模式”?
通俗的来讲就是有一个程序员写了一份代码,他觉得自己写的非常好,可以供他人借鉴,于是就对这种写法取一个名字,比如”适配器模式“。“设计模式”就是对通用代码取一个名字而已。
为什么要有设计模式呢?
为了不让你重复你自己(Don't Repeat Yourself - DRY 原则)
什么是MVC?
如何设计一个程序的结构叫做"架构模式"(architectural pattern)。MVC模式就是架构模式的一种,它对我后面框架的学习启发很大。我觉得这种模式也可以应用于其他领域。
下面是我个人对MVC模式的一些个人理解,有的地方不一定真确,仅供思路理解作参考。
提问:你见过面条式代码吗?什么样的代码是面条式的?
比如像下面这种代码:
$("button").prop("disabled", true)
$("textarea").on("input", function() {
if ($(this).val().length > 0) {
$("button").prop("disabled", false)
} else {
$("button").prop("disabled", true)
}
})
你如果不学MVC,你将写出面条式的烂代码。不停的重复自己、不懂得抽象思维、只会调API、不懂封装、不会造轮子,不能提升自己!!!
讲了这么多,就是希望你能学习MVC设计模式,对你以后的发展会有更大的帮助。
MVC定义:MVC分别是Model(模型)、View(视图)和Controller(控制)。该模式认为,无论你的程序多么简单或复杂,从结构上看,都可以分成三层。
- Model(模型):负责操作所有数据
- View(视图):负责所有 UI 界面
- Controller(控制):负责其它相应操作
这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口,供上面一层调用。这样一来,软件就可以实现模块化,修改界面或者变更数据都不用修改其他层,大大方便了维护和升级。
下面请看<代码一>→<代码二>的抽离过程
代码一:
//初始化html
const html = `
<div>
<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>
</div>
`
const $element = $(html).appendTo($('body').page)
//获取元素
const $button1 = $('#add1')
const $button2 = $('#minus1')
const $button3 = $('#mul2')
const $button4 = $('#divide2')
const $number = $('#number')
//初始化数据
const n = localStorage.getItem('n')
//将数据渲染到页面
$number.text(n || 100)
//绑定鼠标事件
$button1.on('click', () => {
let n = parseInt($number.text())
n += 1
localStorage.setItem('n', n)
$number.text(n)
})
$button2.on('click', () => {
let n = parseInt($number.text())
n -= 1
localStorage.setItem('n', n)
$number.text(n)
})
$button3.on('click', () => {
let n = parseInt($number.text())
n *= 2
localStorage.setItem('n', n)
$number.text(n)
})
$button4.on('click', () => {
let n = parseInt($number.text())
n /= 2
localStorage.setItem('n', n)
$number.text(n)
})
代码二:
const eventBus = $(window) //拥有.on(监听事件)函数/.trigger(触发事件)函数
// 数据相关都放到M
const m = {
data: {
n: parseInt(localStorage.getItem('n'))//初始化数据
},
create() {},
delete() {},
update(data) {
Object.assign(m.data, data)
eventBus.trigger('m:updated')//m跟新了,会触发(事件不能有空格)
localStorage.setItem('n', m.data.n)
},
get() {}
}
// 视图相关都放到V
const v = {
el: null,
html: `
<div>
<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>
</div>
`,
init(container) {//外面传入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
从代码一到代码二,你会发现变复杂了,像代码1那样写的多清楚明了,代码2这里那里的,都绕晕了。。。那是你还没有发现MVC的好处。
在代码2中,所有的属性都是经典的、缺一不少的,你可以在任何地方使用(万金油),代码2在代码1的基础上进行了抽象的转化,代码的简化,看起来复杂了,实际上变得简单可复用了。
EventBus 对象间的通信
jQuery中有一个.on()和.trigger()方法,分别用来监听和触发。如果我在一个地方监听事件,一个地方触发事件,那么这两处不是实现了通信吗。 jQuery API具体实现
用法:
$( "p" ).on( "myEvent", function( event, param ) {
console.log(param)
})
$( "p" ).trigger( "myEvent", [ "123" ] )
下面我们自己实现eventButs(借用了jQuery)
import $ from 'jquery'
class EventButs {
constructor() {
this._eventBus = $(window)
}
//监听函数
on(eventName, fn) {
return this._eventBus.on(eventName, fn)
}
//触发函数
trigger(eventName, data) {
return this._eventBus.trigger(eventName, data)
}
//取消监听
off(eventName, fn) {
return this._eventBus.off(eventName, fn)
}
}
export default EventButs
const e = new EventButs ()
e.on()
e.trigger()
e.off()
表驱动编程
定义:当你看到大批类似但不重复的代码时,你需要看看到底哪些才是重要的数据,把重要的数据做成哈希表,你的代码就简单了。
下面的代码可以使用哈希表简化:
bindEvents() {
v.el.on('click', '#add1', () => {
m.data.n += 1
v.render(m.data.n)
})
v.el.on('click', '#minus1', () => {
m.data.n -= 1
v.render(m.data.n)
})
v.el.on('click', '#mul2', () => {
m.data.n *= 2
v.render(m.data.n)
})
v.el.on('click', '#divide2', () => {
m.data.n /= 2
v.render(m.data.n)
})
}
简化后的:
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)
}
}
}