MVC和重构
MVC 是什么
- M-Model (数据模型)
负责操作所有数据
- V-View (视图)
负责所有 UI 界面
- C-Controller(控制器)
负责其他
// 数据放在m
const m = {
data: {},
// 对数据增删改查
create() {},
delete() {},
update(data) {},
get() {},
};
// 视图放在v
const v = {
el: null,
html: ``,
init(container) {
v.el = $(container);
},
// 渲染函数,参数将是数据。视图全都是对数据渲染 view = render(data)
render(x) {},
};
// 其他放在c
const c = {
init(container) {},
events: {},
add() {},
minus() {},
mul() {},
div() {},
// 自动绑定事件
autoBindEvents() {},
};
代码重构
最小知识原则
- 引入一个模块需要引入 html、css、 js
- 引入一个模块需要引入 html、js
- 引入一个模块需要引入 js
- 你需要知道的知识越少越好,模块化为这一点奠定了基础
在 ES6 中,可以使用 import 关键字引入模块,通过 export 关键字导出模块
表驱动编程: 自动绑定事件
- 利用遍历哈希表,把元素绑定上事件
//自动绑定事件
// 把所有事件写成哈希表
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]]; // 要执行的函数 如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);
}
}
事不过三
- 同样的代码写三遍,就应该抽成一个函数
如表驱动编程的自动绑定事件和下面的例子
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.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 {
constructor(options) {
super();
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
// 旧代码
const eventBus = $(window);
eventBus.trigger("m:updated");
eventBus.on("m:updated", () => {
// 监听m:updated事件,每次触发就重新用新数据渲染一遍
v.render(m.data.n);
});
现在,让所有类都继承 EventBus 类,那每个类的具体的对象就可以直接使用
.on
,.off
,.trigger
了
view = render(data)
- 视图是对数据的渲染。
每次数据改变就重新渲染一次
- 比起操作 DOM 对象,直接 render 简单多了
- 只要改变 data,就可以得到对应的 view
render(x) {
if (v.el.children.length !== 0) v.el.empty();
// 把html里的占位符替换成x,再加入容器中
$(v.html.replace("{{n}}", x)).appendTo(v.el);
}
};
vue
不是这样,但是react
是这样
代价:render 粗犷的渲染肯定比 DOM 操作浪费性能,这里其实最好用虚拟 DOM ,虚拟 DOM 能让 render 只更新该更新的地方
代码示例:
扩展阅读: 前端 MVC 变形记 | EFE Tech