目录
前言
long long ago,我是做iOS的,当时使用MVC做开发。之后借助学习Android的机会认识了MVP模式,最后又因为各种阴差阳错接触了MVVM。由于后两者接触的时间不长,所以对他们始终是有点一知半解,借此机会系统的整理一下,顺便做下记录。
阅读目标
- 前端使用MVC
- 前端使用MVP
- 前端实现MVVM
MVC
Modal
和View
不可直接通信,通过Controller
作为他们之间的桥梁,如下:
- 在Controller发送请求或响应事件,获取Modal
- 在Controller将Modal发送给View从而更新UI
index.html
<html>
<script src='./mvc/Container.js'></script>
<script src='./mvc/View.js'></script>
<script src='./mvc/Modal.js'></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<body>
<h1 id='num'></h1>
<button id='increase'>增加</button>
<button id='decrease'>减小</button>
</body>
</html>
<script>
// ##################### MVC #######################
window.onload = function () {
// MVC的入口一般是Controller层
let controller = new MyApp.Controller();
controller.init();
}
</script>
Container.js
const MyApp = {}; // 创建这个应用对象
MyApp.Controller = function() {
let modal = null;
let view = null;
this.init = function() {
/* 初始化Modal和View */
modal = new MyApp.Modal();
view = new MyApp.View(this);
view.render(modal);
};
/* 让Modal更新数值并通知View更新视图 */
this.increase = function() {
modal.add(1);
view.render(modal);
};
this.decrease = function() {
modal.sub(1);
view.render(modal);
};
};
Modal.js
MyApp.Modal = function() {
let val = 0;
this.add = function(v) {
if (val < 100) val += v;
};
this.sub = function(v) {
if (val > 0) val -= v;
};
this.getVal = function() {
return val;
};
};
View.js
MyApp.View = function(controller) {
let $num = $('#num');
let $incBtn = $('#increase');
let $decBtn = $('#decrease');
this.render = function(modal) {
$num.text(modal.getVal());
};
/* 绑定事件 */
$incBtn.click(controller.increase);
$decBtn.click(controller.decrease);
};
说点啥
关于优势就是实现了代码分层,没啥可多说的,我主要提一点不足。认真观察Container可以发现内部还耦合了View层,这就影响到我们Container层的复用。
举个栗子,有A和B两个项目,A处于迭代期间,B处于初始期,B现在要做登录模块,除了UI,跟项目A的登录模块完全一致,如果A的登录模块的Container层不包含View层的内容的话,那我们可以直接在项目B中使用A模块的Container层,避免做重复的轮子。解决方案的话就是下面要说的MVP模式
MVP
快上车,更多详情在代码中解释
- View与Modal不发生联系,都通过Presenter传递
- Presenter与另外两层的通信都是双向的
- View层需要遵循一定的协议(为了方便复用P层)
index.html
<html>
<script src='./mvp/Presenter.js'></script>
<script src='./mvp/BaseView.js'></script>
<script src='./mvp/View.js'></script>
<script src='./mvp/Modal.js'></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<body>
<h1 id='num'></h1>
<button id='increase'>增加</button>
<button id='decrease'>减小</button>
</body>
</html>
<script>
// ##################### MVP #######################
window.onload = function () {
// MVP的入口一般是View层
var view = new MyApp.View();
view.init();
}
</script>
BaseView.js
const BaseView = function () {
this.render = function () {
// 模拟接口
console.error('子类必须实现的方法');
}
}
View.js
class View extends BaseView {
constructor() {
super();
this.$num = $('#num');
this.$incBtn = $('#increase');
this.$decBtn = $('#decrease');
}
/* 看View的定义处可以发现,该View是继承自BaseView的,作用如下:
* 1. 在创建P时将View的引用,注意是View的引用传给了P,在P层要使用传进来的View更新UI,那么肯定要指定View层都有哪些接口啊,所以就通过BaseView来进行定义。
* 2. 在复用P时,一般要带上BasaeView接口,这样可以快速知道该P都调用了View的哪些接口,也方便View来了解都需要实现什么接口
*
*/
render = function(val) {
this.$num.text(val);
}
init = function() {
// 在View中创建创建Presenter并将View的引用传给Presenter对象,这样就实现了View和Presenter的解耦,方便该Presenter在其他项目中的同模块中复用
var presenter = new MyApp.Presenter(this);
this.$incBtn.click(presenter.increase);
this.$decBtn.click(presenter.decrease);
}
}
MyApp.View = View;
Presenter.js
const MyApp = {};
MyApp.Presenter = function(view) {
let _modal = new MyApp.Modal();
let _view = view;
_view.render(_modal.getVal());
this.increase = function() {
_modal.add(1);
_view.render(_modal.getVal());
};
this.decrease = function() {
_modal.sub(1);
_view.render(_modal.getVal());
};
};
Modal.js
MyApp.Modal = function() {
let val = 0;
this.add = function(v) {
if (val < 100) val += v;
};
this.sub = function(v) {
if (val > 0) val -= v;
};
this.getVal = function() {
return val;
};
};
说点啥
- MVP弥补了MVC中C层不可复用的问题,其他也没啥
- 在了解MVC的基础上将上面MVP的示例自己敲写一遍理解会更加深刻。
MVVM
- 理论上分为三层
Modal
、View
、ViewModal
,主要是让ViewModal完全代替Controller,但现实是Controller不能完全被ViewModal代替 - Modal和View没有什么好说的,主要提一下ViewModal。其实ViewModal的本质是一个Modal,他的主要任务是把纯Modal数据转化成View用Modal。
index.html
<html>
<script src='./MVVM/ViewModal.js'></script>
<script src='./MVVM/View.js'></script>
<script src='./MVVM/Modal.js'></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<body>
<input id="userName" placeholder="账号"/>
<input id="pwd" placeholder="密码"/>
<button id='login'>login</button>
<h5 id="tip"></h5>
</body>
</html>
<script>
// ############## MVVM-当前文件是尚未被完全取代的Container层 ###############
window.onload = function () {
let modal = new MyApp.Modal();
// 创建ViewModal
let viewModal = new MyApp.ViewModal(modal);
$("#userName").on('input', function(e) {
// View-Modal的数据绑定
modal.userName = e.currentTarget.value;
// 使用ViewModal中的getInputState来对输入值做验证
let inputState = viewModal.getInputState();
$('#tip').html(inputState);
});
$("#pwd").on('input', function(e) {
// View-Modal的数据绑定
modal.pwd = e.currentTarget.value;
// 使用ViewModal中的getInputState来对输入值做验证
let inputState = viewModal.getInputState();
$('#tip').html(inputState);
});
$("#login").click(function() {
// Modal-View的数据绑定
let inputState = viewModal.getInputState();
const loginResult = viewModal.login();
if (loginResult && inputState == INPUT_STATE.valid) {
$('#tip').html('登录成功');
return;
}
$('#tip').html('登录失败');
});
}
</script>
View.js
指的就是我们的html代码,不在单独写文件了
Modal.js
MyApp.Modal = function(userName, pwd) {
this.userName = '';
this.pwd = '';
};
ViewModal.js
const MyApp = {}; // 创建这个应用对象
const INPUT_STATE = {
empty: 'empty',
invalid: 'invalid',
valid: 'valid',
}
MyApp.ViewModal = function(modal) {
this.modal = modal;
this.getInputState = function () {
const userNameLength = this.modal.userName.length;
const pwdLength = this.modal.pwd.length;
if (userNameLength == 0 || pwdLength == 0) {
return INPUT_STATE.empty;
}
if ((userNameLength < 5 || userNameLength > 10) || (pwdLength < 5 || pwdLength > 10)) {
return INPUT_STATE.invalid;
}
return INPUT_STATE.valid;
}
this.login = function () {
if (this.modal.userName == 'admin' && this.modal.pwd == 'admin') {
return true;
}
return false;
}
};
说点啥
- 在写代码之前提到ViewModal就是将纯Modal转化成View要用的Modal,在上述示例中,转化如下:
- 将Modal中的userName和pwd转化成ViewModal中getInputState返回的inputState和login返回的loginResult, 使用得到的
inputState
和loginResult
帮助我们实现UI的变化。