MVC的概念
MVC
设计模式是个框,什么都能往里装
前端早期的MVC框架有 Backbone.js、Angular.js等。
回顾下,设计模式的定义
设计模式是啥?
- 老子这个代码写的太漂亮了,别人肯定也用得到
- 那就给这种写法取个名字把,比如适配器模式
- 设计模式就是对通用代码娶个名字而已
- ——《JQuery中的设计模式》
为什么要有设计模式
Don't Repeat Youself——RPY原则
哪里重要啦?
代码级别的重复
- 你把相同额三行代码写了两遍
- 那么你就应该重构他
页面级别
- 你把类似的页面做了10遍
- 那么你就应该想出一个万金油写法
MVC就是一个万金油
所有的页面都可以使用MVC来优化代码结构
MVC的定义并不明确
我不学MVC又怎样?
意大利面条式代码
老手程序员为了鄙视烂代码,将其称为面条式代码
你叫变成外包式程序员
- 不停重复自己,不懂得抽象
- 只会调用API,不能提升自己
- 只会写业务,不会封装,更不会造轮子,更不会加薪
我学还不行吗?!
MVC是啥?
每个模块都可以写成三个对象,分别是M、V、C
M-Model(数据模型)负责操作所有数据
V-View(视图)负责所有UI界面
C-Controller(控制器)负责其他
![7991330eda7f3ba13696505b6328055b.png](https://img-blog.csdnimg.cn/img_convert/7991330eda7f3ba13696505b6328055b.png)
就这?
是的,意会
MVC没有严格的定义
M、V、C分别要做什么也是很随意的,大概对就行
MVC 三个对象分别做什么
伪代码示例
M:把数据相关的操作封装到m对象中,使代码结构更为清晰。
Model(数据模型表示程序需要操作的数据或信息,指的所有数据放一块
const m = {
data: { 程序需要操作的数据或信息 },
create: { 增数据 },
delete: { 删数据 },
update(新数据) {
Object.assign(旧数据, 新数据) //使用新数据替换旧数据
eventBus.trigger('事件名称') // eventBus 触发'事件', 通知 View 刷新
},
get:{ 获取数据 }
}
——————————————————————————————————————————————
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:把视图的显示和渲染放到对象v里。
View(视图)负责所有用户的页面外观,操作界面,是指所有的看的见的视图
const v = {
el: '要挂载视图的选择器', //需要刷新的元素
html: `视图的 html 模版` // 初始化html,显示在页面上的内容
,
init(container) {
v.el = $(container) //需要刷新的元素
},
render(n) { 刷新页面 }
}
————————————————————————————————————————————————————
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:通过创建对象c并利用对象c里的操作对m里的数据进行再次渲染,进而影响视图。
Controller(控制器), 负责除了M和V的事情的其他事情,根据用户从View输入的指令,选取Model中的数据,然后对其进行相应的操作,产生最终结果。
const c = {
init(container) {
v.init(){ 初始化相关逻辑 } // View初始化
v.render() // 第一次渲染
c.autoBindEvents() // 自动的事件绑定
eventBus.on('m:update', () => { v.render() }) // 当eventBus触发'm:update'时View刷新})
},
events: { 事件以哈希表方式记录 },
add() {详细执行},
minus() {详细执行},
mul() {详细执行},
div() {详细执行},
autoBindEvents() { 自动绑定事件 }
}
}
——————Vue——————
const c = {
init(container) {
v.init() // View初始化
v.render() // 第一次渲染
c.autoBindEvents() // 自动的事件绑定
eventBus.on('m:update', () => { v.render() }) // 当eventBus触发'm:update'时View刷新})
},
events: { 事件以哈希表方式记录 },
method() { //处理数据的一些方法
data = 改变后的新数据
m.update(data)
},
autoBindEvents() { 自动绑定事件 }
}
}
——————————————————————————————————————————————
const c = {
init(container) {
v.init(container)
v.render(m.data.n)
c.autoBindEvents()
eventBus.on('m:updated', () => {
v.render(m.data.n)
})
}
}
伪代码示例(类)
//数据层 Model
class Model{
data:数据,
getDate(){
获取数据操作
}
update(data){
数据更新操作
}
}
//视图层 View
Class View{
ele: 视图元素,
//把视图元素渲染到页面中
render()
}
//控制层 Controler
Class Controler{
//获取Model的引用
//获取View的引用
m = getModel()
v=getView()
//通知视图层更新数据,重新渲染
v.render(m.data)
...
//视图层的数据变化时候,通知数据层改变data
m.update(newdata)
}
四个需求的描述
![f33b5186d4bd8f815a7c495a080d9ffd.png](https://img-blog.csdnimg.cn/img_convert/f33b5186d4bd8f815a7c495a080d9ffd.png)
实现四个简单需求
- 带数据储存的计算器
- 带数据储存的Tab切换
- 位移变形动画
- 变色动画
(MVC)优化四个需求
- 带数据储存的计算器
- 带数据储存的Tab切换
- 位移变形动画
- 变色动画
(大师级)继续优化四个需求
- 带数据储存的计算器
- 带数据储存的Tab切换
- 位移变形动画
- 变色动画
设置每个模块的数据保存
app2.js:添加的代码!
const index = localStorage.getItem('app2.index') || 0 //本地储存localStorage
localStorage.setItem('app2.index',index) //设置
$tabBar.children().eq(index).trigger('click')//自己点击
简化代码
const active = localStorage.getItem(localKey) === 'yes'
// const active = localStorage.getItem(localKey) === 'yes' ? true : false
if (active){
$square.addClass('active')
}else{
$square.removeClass('active')
}
// 以上代码等价于 -----------> $square.toggleClass('active',active)
EventBus
即事件总线,用于模块间的通讯, view 组件层面,父子组件、兄弟组件通信都可以使 eventbus 处理
简而言之,就是用来事件通信
EventBus 事件发布-订阅总线,提供事件发布、订阅等功能
例如:
// 订阅者
$(document).on('say', function() { console.log('hi')})
// 发布者
$(document).trigger('say')
// 其中 say 是事件
EventBus 有哪些 API,是做什么用的?
使用EventBus可以实现对象间的通信。
EventBus 基本的 API 有 on (监听事件), trigger (触发事件), off (取消监听)方法。
on('事件类型',fn)事件的绑定
off('事件类型',fn)事件的销毁
trigger(fn) 事件的触发
//或者emit(fn)
——————————————————————————————————————————————
// 伪代码
eventBus.触发事件('事件名称')
eventBus.监听事件('事件名称', function () { 处理逻辑 })
eventBus.取消事件('事件名称')
——————————————————————————————————————————————
const eventBus=$(window) //引入jquery中的eventBus
eventBus.trigger('m:updated') //页面重新加载时触发'm:updated'事件
eventBus.on('m:updated', () => {
v.render(m.data.n)a
}) //监听'm:updated'事件
on:一般写在controler层
eventBus.on('m:updated', () => {
v.render(m.data.index)
})
trigger:一般写在model层
update(data) {
...
eventBus.trigger('m:updated')
...
},
class伪代码示例
constructor(){
this._eventBus =$(window)
}
on(eventName, fn){
return this._eventBus.on(eventName,fn)
}
trigger(eventName,data){
return this._trigger.tiggger(eventName,data)
}
off(eventName, fn){
return this._eventBus.off(eventName,fn)
}
}
export default EventBus
总结
- 当一个事件执行,eventBus触发 m:updatedeventBus.trigger('m:updated')
- eventBus监听m:updated
- 当m:updated触发时,执行一些内容
eventBus.on('m:updated',()=> {
v.render(m.data.n)
})
表驱动编程是做什么的
所谓表驱动法(Table-Driven Approach),是指用查表的方法获取值。
表驱动法是一种编程模式(scheme)——从表里面查找信息而不使用逻辑语句(if和case)。事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越发杂,查表法也就愈发显得更具吸引力。
顾名思义通过哈希表等来驱动一系列事件、函数的变成。使代码变得更简洁,易于维护。
有什么好处?
数据驱动的一些优势:
- 可读性更强
- 更容易修改,也就是说易于扩展,能够更好的面向变化
- 更易重用
const level = {
1: '很差',
2: '差',
3: '一般',
4: '好',
5: '很好'
}
function getLevel(n) {
return level[n]
}
减少重复代码(参考MVC)
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)
})
}
(哈希)表驱动编程改写为:
events:{
'click #aa1':'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(自动绑定事件)
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)
}
——————————————————————————————————————————————————
autoBindEvents() {
for (let key in events) {
if (events.hasOwnProperty(key)) {
const spaceIndex = key.indexOf(' ')
const part1 = key.slice(0, spaceIndex)
const part2 = key.slice(spaceIndex + 1)
const value = events[key]
元素.on(part1, part2, value) // 自动绑定所有事件
}
}
}
如何理解模块化的
为什么要模块化
javascript这门语言被创造出来时,它只是基于浏览器的。但自1996年以来,已经有各种各样的JavaScript平台可以在浏览器之外进行编程,官方定义的API只能构建基于浏览器的应用程序。人们逐渐认识到了问题的严重性:
- JavaScript没有模块系统。要编写JavaScript脚本,必须以HTML格式管理,连接,注入或手动获取和评估它们。没有用于范围隔离或依赖关系管理的本机工具。
- JavaScript没有标准库。它有浏览器API,日期和数学,但没有文件系统API,更不用说IO流API或二进制数据的原始类型。
- JavaScript没有Web服务器或数据库之类的标准接口。
- JavaScript没有管理依赖关系并自动安装它们的软件包管理系统,除了JSAN(不要与JSON混淆),这不符合范围隔离。
模块化
首先什么是模块,模块就是把一个复杂的程序按照特定的规则拆分,按照特定的规范封装成几个块(文件)。要满足封装性、复用性、扩展性这三个方面的要求。在web前端(此处针对ES6规范)中的写法,如:
// Model1.js// .....
export default Model1
// Model2.js// ....
export default Model2
//main.js
import Model1 from 'Model1.js'import Model2 from 'Model2.js'// ...
我认为模块化主要是为了实现最小知识原则,最小知识原则就是让使用者不需要了解这么多的实现细节。
所谓模块化,就是把某个功能写好放在一个模块里,再使用import引入。
当要实现某一页面的功能时,如果功能较少尚且好说,但若包含多个功能,如把多个功能的实现写在一个文件里,则不免显得混乱,缺乏可读性。同时,在代码初步完成后对某一功能的修改,甚或影响其他功能。
所以,为了避免代码混乱和功能的交叉影响,就需要把各个不同的功能分不同的模块,写在不同的文件,再进行引入使用。如此一来,不仅代码逻辑层次明晰,同时便于勘误和修改。
其主要思想非常简单,即把每个功能分别写在各自的不同文件里,最后再在主文件引入即可。可按一棵从枝叶到根部各个分叉逐步汇集的逻辑树形图来理解。
解耦
最关键的地方就在于解耦,所谓的解耦:解耦即接触耦合关系,耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。模块化有利于解耦,是因为每个模块封装了各自的功能,大家各干各的,相互协作时不必太过关注内部实现问题。
模块化的好处:
- 避免变量污染,命名冲突
- 提高代码复用率
- 提高维护性
- 依赖关系的管理
例如:实现一个App
需要引入HTML ,JS,CSS文件,但如果只引入一个JS,就能引入全部,这就是最小知识,对于JS这门脚本语言来说,模块化很重要,可以在一定程度上解耦合,方便代码维护
export default xxx
import xxx form './xxx.js'
![9e80da241fb9f7dad3395353e7d9de3c.png](https://img-blog.csdnimg.cn/img_convert/9e80da241fb9f7dad3395353e7d9de3c.png)
![a2b0a6055611bc911cc4e55cecdf5265.png](https://img-blog.csdnimg.cn/img_convert/a2b0a6055611bc911cc4e55cecdf5265.png)
MVC原理思路
抽象思维1
最小知识原则
- 引入一个模块需要引入 html、css、js
- 引入一个模块需要引入 html、js
- 引入一个模块需要引入 js
- 你需要知道的知识越少越好
- 模块化为这一点奠定了基础
代价
- 这样做会使得页面一开始是空白的,没内容没样式
- 解决方法很多,比如加菊花、加骨架、加占位内容等
- 也有人选择用 SSR 技术来解决,我个人觉得没必要
通过模块化改写,将页面中每一个板块分成一个个独立的模块;单独写它的Css、JS最后在main.js中将这些模块引入即可。如:
import $ from 'jQuery'//引入jQuery
import './app.css'//引入独立的css
import './app.js'//引入独立的JS
上面这个引入还可以简写,我们把独立的css放在JS文件中,在JS文件开头引入css
import './app.css'
最后,将整个JS文件节点导出
export default x
为了防止元素替换后带来的按键失效:
用到的方法事件绑定:我们把元素绑定在最外面的哪一个元素上面,利用父级元素对子元素的监听!
抽象思维2
以不变应万变
- 既然每个模块都可以用 m + v + c 搞定
- 那么每个模块我就都这样写就好啦
- 不用再思考类似的需求该怎么做了
代价
- 有时候会有一些多余的用不到代码
- 有时候遇到特殊情况不知道怎么变通,比如没有 html 的模块怎么做 mvc
![9443166110d9bc18fe6772af61b26e73.png](https://img-blog.csdnimg.cn/img_convert/9443166110d9bc18fe6772af61b26e73.png)
抽象思维3
表驱动编程
- 当你看到大批类似但不重复的代码
- 眯起眼睛,看看到底哪些才是重要的数据
- 把重要的数据做成哈希表,你的代码就简单了
- 这是数据结构知识给我们的红利
代价
没有代价
胶水层
![9b90b1089c62554807c12b1b156fb104.png](https://img-blog.csdnimg.cn/img_convert/9b90b1089c62554807c12b1b156fb104.png)
抽象思维4
事不过三
- 同样的代码写三遍,就应该抽成一个函数
- 同样的属性写三遍,就应该做成共用属性(原型或类)
- 同样的原型写三遍,就应该用继承
代价
- 有的时候会造成继承层级太深,无法一下看懂代码
- 可以通过写文档、画类图解决
继承
EventBus代码
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(eventName,fn){
return this._eventBus.off(eventName,fn)
}
}
export default EventBus
Model代码:
import EventBus from "./EventBus";
class Model extends EventBus{
//处于对象的自身上
constructor(options) {
super()
const keys = ['data','create','delete','update','get']
keys.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')
}
}
export default Model
用法:
m.trigger('m:updated')
抽象思维5
俯瞰全局
- 把所有的对象看成点
- 一个点和一个点怎么通信
- 一个点和多个点怎么通信
- 多个点和多个点怎么通信
- 最终我们找出一个专用的点负责通信
- 这个点就是 event bus(事件总线)
抽象思维6
view = render(data)
- 比起操作 DOM 对象,直接 render 简单多了
- 只要改变 data,就可以得到对应的 view
代价
- render 粗犷的渲染肯定比 DOM 操作浪费性能
- 还好我们后面会用到虚拟 DOM
- 虚拟 DOM 能让 render 只更新该更新的地方
总结
- MVC 的概念
- eventBus 的概念
- view = render(data) 概念
- 表驱动编程概念
- 模块化的概念
- 类什么时候用
- 继承什么时候用
太抽象?
- 记不住怎么办?
- 把这些概念刻意用在项目里
- 并写文章总结
- 然后忘掉它们
未完
- eventBus的概念
- view=render(data)概念
- 表驱动编程概念
- 模块化的概念
- 类什么时候用
- 继承什么时候用
- Controller
- EventHub
路由
![6733049c1ef4c78d31d510be05a4aff0.png](https://img-blog.csdnimg.cn/img_convert/6733049c1ef4c78d31d510be05a4aff0.png)
参考文章
前端MVC变形记 | EFE Techefe.baidu.com![0205be004517b2ea546f1d08c8127b08.png](https://img-blog.csdnimg.cn/img_convert/0205be004517b2ea546f1d08c8127b08.png)