MVC:每个模块都可以写成三个对象
M-Model 数据模型 负责操作所有数据
V-View 视图 负责所有UI界面
C-Controller 控制器 负责其他
//数据放在m
const m = {
//1.有个数据本数
data: {},
//2.可以对数据增删改查
create() {},
delete() {},
update(data) {},
get() {}
}
//视图放在v
const v = {
//1、一个空容器,以后就是装html的容器
el: null,
//2、要添加的html
html: ,
//3、初始化容器函数,参数是我们给的要当容器的元素(应该是index.html里就有的元素)
init(container) {v.el = $(container);},
//4、渲染函数,参数将是数据。也就是视图全都是对数据渲染 view = render(data)
render(x) {}
}
//其他放在c
const c = {
//1.总初始化函数,参数是我们给的要当容器的元素
init(container) {},
//2、自动绑定事件
//(1)把所有事件写成哈希表
events: {},
//(2)每个事件要执行的函数写出来
add() {},
minus() {},
mul() {},
div() {},
//(3)自动绑定事件
autoBindEvents() {}
}
复制代码MVC就是个设计模式,设计模式就是一串优秀的代码,取个名字而已。设计模式的存在就是减少重复代码
MVC用于所有页面优化代码,减少重复
不学MVC你就会写出spaghetti code(面条式代码,烂代码),你就会成为外包程序员,只会重复自己
MVC是我们从面条代码到框架代码的必经之路,vue和react都是从MVC演变而来,只有知道了MVC,才能知道vue和react的原理
抽象思维(MVC设计模式总结,vue)
最小知识原则
- 引入一个模块需要引入html、css、 js
- 引入一个模块需要引入html、js
- 引入一个模块需要引入js
- 你需要知道的知识越少越好,模块化为这一点奠定了基础
- 模块化:在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块
- 例如:都要由main.js引入css reset 、整个页面的css、每个模块的js
每个模块的js引入jquery 、 他的css 、他的html
这样html里面就是空空的

但是有代价
- 所以刚开始加载时白的,只有等js全加载完才会显示在页面上。用户看到白的超过三秒就想关掉了
- (所以前端测试不能用很好的网络,否则你永远不知道你的用户的体验有多差)(开发者工具->network→online,slow 3g→Disable cache 可以查看)
- 解决方法
- 加菊花:先在html里加个菊花图,然后在main.js里
img.remove() - 加骨架

以不变应万变
- 既然每个模块都可以用m+v+ C搞定 那么每个模块我就都这样写就好啦 不用再思考类似的需求该怎么做了
- 代价
有时候会有一些多余的用不到代码
有时候遇到特殊情况不知道怎么变通,比如没有html 的模块怎么做
太复杂的还是得用MVC,vue就不适合了
表驱动编程
- 当你看到大批类似但不重复的代码
- 眯起眼睛,看看到底哪些才是重要的数据
- 把重要的数据做成哈希表,你的代码就简单了
- 这是数据结构知识给我们的红利
- 我们只要把填完整,vue已经把表外面的事情做好了
例如 表驱动编程:自动绑定事件
- 表指的是哈希表
- 表驱动编程可以减少重复代码,只讲重要的信息放在表里,然后利用表来编程
- 利用哈希表,把每一个的事件,元素,函数列出来
- 把每一个的函数定义出来
- 自动绑定事件:利用遍历哈希表,把元素绑定上事件。
//自动绑定事件
//(1)把所有事件写成哈希表
events: {
"click #add1": "add",
"click #minus1": "minus",
"click #mul2": "mul",
"click #divide2": "div"
},
//(2)每个事件要执行的函数写出来
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 });
},
//(3)自动绑定事件
autoBindEvents() {
for (let key in c.events) {
//对事件哈希表里每一个事件
const value = c[c.events[key]]; //要执行的函数 如add
const spaceIndex = key.indexOf(" ");
const part1 = key.slice(0, spaceIndex); //事件名part1 如click
const part2 = key.slice(spaceIndex + 1); //实际监听元素part2 如#add1元素
v.el.on(part1, part2, value); //用事件委托,监听容器元素的part1事件,其实是监听他的子元素part2的part1事件,执行函数。
//这里是用容器元素的子元素的id来选出子元素然后绑定事件给这些子元素,每次渲染子元素的id不会变的。所以用子元素id选出子元素在绑定事件,一劳永逸!
}
}
复制代码事不过三
- 同样的代码写三遍,就应该抽成一个函数,如表驱动编程的自动绑定事件和下面的例子
class Model {
constructor(options) {
const keys = ['data', 'update', 'create', 'delete', 'get']
keys.forEach((key) => {
if (key in options) {
this[key] = options[key]
}
})
}
update(){}
create(){}
delete(){}
get(){}
}
//烂代码:超过三个,用遍历
class Model {
constructor(options) {
this.data = option.data
this.update = option.update
this.create = option.create
this.delete = option.delete
this.get = option.delete
//这里没有超过三个,就不用遍历,写成解构形式的对象最好
class View {
constructor ({el,html,render}){
this.el = $(el)
this.html = html
this.render = render
}
}
复制代码- 同样的属性写三遍,就应该做成共用属性(原型或类) , 如MVC变成Model类、View类
- 同样的原型写三遍,就应该用继承:如EventBus
//在EventBus.js中
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.js中
import EventBus from './EventBus'
class Model extends EventBus { //让Model类继承EventBus类的所有属性
constructor(options) {
super() //特别的。要这样把EventBus的constructor弄过来
const keys = ['data', 'update', 'create', 'delete', '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
复制代码- 代价:有的时候会造成继承层级太深,无法一下看懂代码。可以通过写文档、画类图解决
俯瞰全局
- 把所有的对象看成点
- 一个点和一个点怎么通信
- 一个点和多个点怎么通信
- 多个点和多个点怎么通信
- 最终我们找出一个专用的点负责通信
- 这个点就是eventBus (事件总线)
- vue是EventTarget,有
v.on()v.emit()v.off()
- 以前我们是
const eventBus = $(window) //在全局
eventBus.trigger("m:updated"); //在数据增删改查函数中
eventBus.on("m:updated", () => {
//④监听m:updated事件,每次触发就重新用新数据渲染一遍
console.log("here");
v.render(m.data.n);
})
复制代码- eventBus 提供了 on、off 和 trigger 等 API,on 用于监听事件,trigger 用域触发事件
- eventBus 主要用于对象间通信
- 使用 eventBus 可以满足最小知识原则,m 和 v 互相不知道对方的细节,
- 数据更新,就重新渲染一次。那怎么知道数据更新了?在数据更新函数加上触发eventBus的xxx事件,那么监听eventBus的xxx事件,要是触发了就重新用新数据渲染一次。
- 数据更新 → 触发eventBus的xxx事件 → 执行xxx事件监听函数:用新数据再次渲染
- 现在 让所有类都继承了EventBus类,那每个类的具体的对象就可以直接使用.on .off .trigger 了
view = render(data)
- 视图是对数据的渲染。每次数据改变就重新渲染一次
- 比起操作DOM对象,直接render简单多了
- 只要改变data,就可以得到对应的view
render(x) {
if (v.el.children.length !== 0) v.el.empty(); //如果容器里有东西,就全删掉
$(v.html.replace("{{n}}", x)).appendTo(v.el); //把html里的占位符替换成x,再加入容器中
}
};
复制代码- vue不是这样,但是react是这样
- 代价:render粗犷的渲染肯定比DOM操作浪费性能,还好我们后面会用到虚拟DOM ,虚拟DOM能让render只更新该更新的地方