浅析MVC

什么是MVC

MVC包括三个对象,分别是Model、View、Controller,通过将代码进行模块化的分离,以便于提高代码的灵活性和复用性。

  • M-model数据模型,负责与数据相关的任务(包括增删改查),会有一个或多个视图监听该模型,一旦数据发生改变,模型将通知相关视图
  • V-view视图层,是提供给用户操作的界面,当model层的数据发生改变,视图也会相应的进行更新
  • C-controller控制器,起到在不同层面的组织作用。通过监听用户事件,来处理用户行为和数据上的改变

Model层:负责操作数据

1
2
3
4
5
6
7
8
9
10
let Model = {
data: {},
create: {},
delete: {},
update(data){
Object.assign(m.data, data) // 更新数据
eventBus.trigger('m:update') // 触发'm:update'并通知view更新
},
get: {}
}

View层:负责视图

1
2
3
4
5
6
7
8
let View = {
el: el, // 需要更新的元素
html: ``, // 显示在页面中的内容
init(){
v.el: // 初始化需要更新的元素
},
render(){} // 重新渲染
}

Controller层:负责其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let Controller = {
init(){
v.init(), // 初始化view
v.render(), // 第一次渲染页面
c.autoBindEvents(), // 事件绑定
eventBus.on('m:update', ()=>{ // 当eventBus触发'm:update'时,刷新view
v.render()
})
},
events: { // 用哈希表储存事件信息
'click #add1' : 'add',
'click #minus': '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})
},
methods(){
data=新数据
m.update(data) // 通知model更新数据
},
autoBindEvents(){
for(let key in c.events){ // 遍历events表,自动绑定事件
const value = c[c.events[key]]
const spaceIndex = key.indexOf('')
const part1 = key.slice(0, spaceIndex) // 拿到'click'
const part2 = key.slice(spaceIndex + 1) // 拿到对应元素'#add1'、'#minus'、'#mul2'、'#div2'
v.el.on(part1, part2, value)
}
}
}

EventBus

EventBus用于模块间通讯,常用的api包括事件触发(trigger)、事件监听(on)、取消监听(off)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import $ from 'jquery'

class EventBus {
constructor() {
this._eventBus = $(window)
}
on(eventName,fn){
return this._eventBus.on(eventName,fn)
}
trigger(eventName,data){
return this._eventBus.trigger(eventName,data)
}
off(){
return this._eventBus.off(eventName,fn)
}
}

export default EventBus

// new.js
import EventBus from 'EventBus.js'
const e = new EventBus()
e.on()
e.trigger()
e.off()

表驱动编程

表驱动是一种编程模式,从哈希表里查找信息而不是重复使用if…else语句。对于简单的情况,使用逻辑语句更加轻松直白,但对于复杂的逻辑链来说,表驱动有着更稳定的复杂度。下面这段代码有着极高的相似度和重复性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$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)
})

用事件委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const c = {
init(container) {
v.init(container)
v.render(m.data.n)
c.bindEvents()
}
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)
})
}
}

用哈希表存储<按钮>及按下按钮对应的<事件>,这样使得数据和事件清晰的分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const c = {
events:{
'click #add1':'add',
'click #minus1':'minus',
'click #mul2':'mul',
'click #divide2':'div'
},
add(){
m.update( data: {n:m.data.n + 1})
},
minus(){
m.update( data:{n:m.data.n - 1})
},
mul(){
m.update( data: {n:m.data.n * 2})
},
div(){
m.update(data: {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)
}
}
}

模块化

  • 将一段复杂的代码依据一定的规则抽取成一个个较小规模的块,块与块之间相对独立
  • “块”可以理解为实现特定功能的一组方法
  • 块的内部数据(变量)与实现(函数)是私有的,只是向外部暴露了一些接口使其与外部其它模块通讯
  • ES6语法中引入import和export来导入和导出模块
    1
    2
    3
    4
    5
    6
    7
    // 导出
    export default c // 默认导出方式
    export {name, draw, reportArea, reportPerimeter} // 导出所有想要导出的模块,使用花括号并用逗号分隔

    // 导入
    import xxx from './app1.js'
    import {name, draw, reportArea, reportPerimeter} from '/js-examples/modules/basic-modules/modules/square.mjs'

事不过三抽象思维

  • 同样的代码写三遍,就应该抽成一个函数
  • 同样的属性写的三遍,就应该做成共用属性(原型或类)
  • 同样的原型写三遍,就应该用继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Model{
    constructor(options){
    ['data','update','create','get'].forEach(key)=>{
    if(key in options){
    this[key] = options[key]
    }
    }
    }
    create(){
    console && console.error && console.error('还没实现 create')
    }
    delete(){
    console && console.error && console.error('还没实现 delete')
    }
    update(){
    console && console.error && console.error('还没实现 update')
    }
    get(){
    console && console.error && console.error('还没实现 get')
    }
    }
0%