一、MVC是什麼?
M 就是 model, 即數據模型,負責數據相關的任務,包括對數據的增刪改查
V 就是view, 即視圖層,即用户能看得到的界面
C 就是 Controller,控制器,負責監聽用户事件,然後調用 M 和 V 更新數據和視圖
接下來將分別用偽代碼表示三個部分的工作內容
1.1 Model 數據模型
//示例
let Model={
data:{數據源},
create:{增加數據},
delete:{刪除數據},
update(data){
Object.assign(m.data,data)//用新數據替換舊數據
eventBus.trigger('m:update')//eventBus觸發'm:update'信息,通知View刷新界面
},
get:{獲取數據}
}
1.2 View 視圖層
//示例
let View={
el:要刷新的元素,
html:'要顯示在頁面上的刷新內容'
init(){
v.el:初始化需要刷新的元素
},
render(){
刷新頁面
}
}
1.3 Controller 控制器
控制器就是通過綁定事件,根據用户的操作,調用M和V 更新數據和視圖
let Controller={
init(){
v.init()//初始化View
v.render()//第一次渲染頁面
c.autoBindEvents()//自動的事件綁定
eventBus.on('m:update',()=>{v.render()}//當enentsBus觸發'm:update'是View刷新
},
events:{事件以哈希表的方式記錄存儲},
//例如:
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})
},
method(){
data=新數據
m.update(data) // controller 通知 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'
v.el.on(part1, part2, value)
}
}
1.4 MVC 實例
我們的目標是做一個加減乘除計算器
每次點擊加、減、乘、除按鈕,數值就會變,基本思想就是監聽click事件
import './app1.css'
import $ from 'jquery'
const eventBus = $(window)
// 數據相關都放到m
const m = {
data: {
n: parseInt(localStorage.getItem('n'))
},
create() {},
delete() {},
update(data) {
Object.assign(m.data, data)
eventBus.trigger('m:updated')
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) {
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', () => {
console.log('here')
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
二、EventBus
2.1 EventBus是什麼?
EventBus主要用於對象之間的通信,比如在上面的例子中,Model 數據模型 和View 視圖模型彼此不知道彼此的存在,但是又需要通信,於是就要用到EventBus
總結:使用 eventBus 可以滿足最小知識原則,m 和 v 互相不知道對方的細節,但是卻可以調用對方的功能
2.2 EventBus 有哪些API?
eventBus 提供了 on、off 和 trigger 等 API,on 用於監聽事件,trigger 用於觸發事件
比如在上面的MVC模型中, M數據模型更新時,會 trigger 觸發一個事件
const m = {
....
update(data) {
Object.assign(m.data, data)
eventBus.trigger('m:updated') // 通知一下view層,我已經更新了數據,view該開始工作了
localStorage.setItem('n', m.data.n)
},
....
}
然後在controller,controller會用 on 監聽事件, 然後通知 view 模型去重新渲染頁面
const c = {
init(container) {
v.init(container)
v.render(m.data.n) // view = render(data)
c.autoBindEvents()
eventBus.on('m:updated', () => { // controller會用 on 監聽事件,
//然後通知 view 模型去重新渲染頁面
console.log('here')
v.render(m.data.n)
})
},
...
}
三、表驅動編程
當我們需要判斷3種以上的情況,做出相應的事情,往往需要寫很多很多的If else,這樣的代碼可讀性不強, 為了增強代碼的可讀性,我們可以用表驅動編程,把用來做If條件判斷的值存進一個哈希表,然後從表裏取值
舉例:
在上面的例子中,加減乘除四個按鈕我需要分別判斷是哪一個按鈕被點擊,再修改output的值,
按照傳統做法, 我們會對四個按鈕分別綁定click事件,然後再分別寫四個回調函數,修改值
$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 = n * 2
localStorage.setItem('n', n)
$number.text(n)
})
$button4.on('click', () => {
let n = parseInt($number.text())
n = n/2
localStorage.setItem('n', n)
$number.text(n)
})
--------用事件委託後-------
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.用哈希表存下按鈕和按鈕對應的操作
const c = {
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)
}
}
四、模塊化
模塊化就是把相對獨立的代碼從一大段代碼裏抽取成一個個短小精悍的模塊
每個模塊之間相對獨立,方便以後的維護和修改
ES6的語法裏引入了Import和export就是用來實現模塊化的
當我們在app1.js 裏封裝好了controller 模型, 然後導出controller:
export default c // 默認導出
export {c} // 另外一種導出方式。記得要加花括號
在Main.js裏我們想用controller:
import x from './app1.js'
等價於import {default as x} from './app1.js'
x.init('#app1')
關於重命名導出的更多例子:
// inside module.mjs
export { function1, function2 };
// inside main.mjs
import { function1 as newFunctionName,
function2 as anotherNewFunctionName } from '/modules/module.mjs';