从MVC、MVP到MVVM的代码实现

前端开发模式已经历从MVC、MVP到MVVM的过渡,然而MVX模式本质都是在解决GUI程序的所面临的各种问题,而核心解决的问题是M和V如何通过X来牵线搭桥实现交互。

本文主要介绍MVX不同模式的原理和简单实现。

设定场景

为了介绍,我们来设定一个交互场景:当我们选择某种动物,就在某个区域显示它的叫声。

 

无模式实现

如果不使用任何模式,我们可以通过如下代码实现:

<select id="animal">
	<option value="chick">chick</option>
	<option value="hen">hen</option>
	<option value="cock">cock</option>
</select>
<div><span id="name"></span>'s voice is <span id="voice"></span>.</div>
document.querySelector('#animal').onchange = function(){
	var voices = {
		chick : 'bi bi',
		hen : 'gu gu',
		cock : 'wo wo'
	};

	var name = this.value, voice = voices[name];
	document.querySelector('#name').textContent = name;
	document.querySelector('#voice').textContent = voice;
};

这样的实现简单暴力,代码量也很少,但是对于单元测试和代码维护是比较大的。

下面我们就开始MVC、MVP和MVVM的实现。(本文主要是代码实现,原理部分详细解释建议大家阅读http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html)

 

MVC模式实现

140239_23VJ_1987188.png

其工作原理实际为:

1.View 传送指令到 Controller

2.Controller 完成业务逻辑后,要求 Model 改变状态

3.Model 将新的数据发送到 View,用户得到反馈

基于这样的原理我们来实现这个业务逻辑:

<select id="animal">
	<option value="chick">chick</option>
	<option value="hen">hen</option>
	<option value="cock">cock</option>
</select>
<div><span id="name"></span>'s voice is <span id="voice"></span>.</div>
var M = {
	voices : {
		chick : 'bi bi',
		hen : 'gu gu',
		cock : 'wo wo'
	},
	name : '',
	voice : '',
	change : function(name){
		this.name = name;
		this.voice = this.voices[name];
		V.update();//调用V
	},
	get : function(k){
		return this[k];
	}
};

var V = {
	init : function(){
		document.querySelector('#animal').onchange= function(){
			C.set(this.value);//调用C
		};
	},
	update : function(){
		document.querySelector('#name').textContent = M.get('name');
		document.querySelector('#voice').textContent = M.get('voice');
	}
};

var C = {
	init : function(){
		V.init();
	},
	set : function(name){
		M.change(name);//调用M
	}
};

C.init();

以上代码很清晰分出M、V和C三个模块,V通过事件通知C控制M的变化,M变化后会调用V进行视图更新,整个流程是单向的。

经过MVC模式的实现,代码分层更清晰,这样对代码的思路清晰,维护也更简单。

然而,MVC模式仍然存在问题:

1.V层很难组件化,界面稍微有一些变动,MVC三层都要联动修改

2.C层单元测试较难,因为C和V耦合较紧,所以想要单独测试C几乎是不可能的事情

 

MVP模式实现

05150524_wqHk.png

其工作原理为:

1. 各部分之间的通信,都是双向的。

2. View 与 Model 不发生联系,都通过 Presenter 传递。

3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

基于以上原理我们通过代码实现如下:

<select id="animal">
	<option value="chick">chick</option>
	<option value="hen">hen</option>
	<option value="cock">cock</option>
</select>
<div><span id="name"></span>'voice is <span id="voice"></span>.</div>
var M = {
	voices : {
		chick : 'bi bi',
		hen : 'gu gu',
		cock : 'wo wo'
	},
	name : '',
	voice : '',
	change : function(name){
		this.name = name;
		this.voice = this.voices[name];
	},
	get : function(k){
		return this[k];
	}
};

var V = {
	init : function(){
		document.querySelector('#animal').onchange = function(){
			C.set(this.value);//调用C
		};
	},
	update : function(kv){
		for(k in kv){
			document.querySelector('#'+k).textContent = kv[k];
		}
	}
};

var P = {
	init : function(){
		V.init();//调用V
	},
	set : function(name){
		M.change(name);//调用M
		V.update({
			name : M.get('name'), 
			voice : M.get('voice')
		});//调用V
	}
};

P.init();

从上面的代码来看跟MVC差不太多,区别主要在于M和V没有直接交互,而是通过P来进行完全控制,所以P也被称为控制狂,任何交互上的事情都由很强的控制欲。

MVP较好的解决前面提到的MVC出现的问题,但是也带来了新的问题,也就是P层会越来越庞大,甚至导致维护困难。

 

MVVM模式实现

05150525_SWc2.png

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

基于上面的原理我们代码实现如下:

<div id="content">
	<select id="animal" data-bind="change:doChange">
		<option value="chick">chick</option>
		<option value="hen">hen</option>
		<option value="cock">cock</option>
	</select>

	<div><span data-bind="name"></span>'voice is <span data-bind="voice"></span>.</div>
</div>
var M = {
	voices : {
		chick : 'bi bi',
		hen : 'gu gu',
		cock : 'wo wo',
	},
	name : '',
	voice : ''
	doChang : function(){
		M.name = this.value;
		M.voice = this.voices[obj.name];
	}
};

var V = {	
	bindEvent : function(el, evt, func){
		el['on'+evt] = M[func]();
	},
	updateText : function(el, text){
		el.textContent = text;
	}
};

var VM = {
	init : function(rootSelector){
		this.observer();
		this.compiler(rootSelector);
	},
	paths : {},
	observer : function(){
		var _this = this;
		for(var k in M){
			(function(k){
				var name = k, value = M[k];
				Object.defineProperty(M, name, {
					get : function(){
						return value;
					},
					set : function(v){
						value = v;
						V.updateText(_this.paths[name], v);
					}
				});
			})(k);
		}
		
	},
	compiler : function(rootSelector){
		var binds = document.querySelector(rootSelector).querySelectorAll('[data-bind]');
		for(var i=0, len=binds.length;i<len;i++){
			var el = binds[i];
			var directive = el.getAttribute('data-bind');
			if(directive.indexOf(':')>-1){//事件指令
				var directives = directive.split(':');
				var evt = directives[0], func = directives[1];
				V.bindEvent(el, evt, func);
			}else{//文本指令
				this.paths[directive] = el;
				V.updateText(el, M[directive]);
			}
		}
	}
};

VM.init('#content');

上面的代码实际只是实现了简单双向绑定,下次在介绍Agile.vm框架的时候再做详解。

从上面的代码看,我们在调用init方法的时候传递了一个选择器,首先对M层数据进行变化检测(observer方法),然后MV再以这个选择器对应的dom作为根节点进行扫描(compiler方法),获取所有VM指令(data-bind)的节点,并缓存起来。

经过这样的设计,M和V已经没有必然关联,只要符合data-bind规则的指令都会被解析,V层的结构无论怎么变化,只要需要有数据绑定的地方加上指令即可。  

内部执行效果为,当select的change事件触发,会调用M改变其name和voice的值,VM层的observer检测到M数据变化调用V的updateText来修改文本内容的变化,从而实现数据的联动。

MVVM模式大大提高代码的可维护性,甚至我们使用Sprite开发的应用切换到HTML5也仅仅只需把V层改变即可,同时也简化了测试,只要确保MVVM框架的准确性,也就是VM的绑定关系准确,整个逻辑就是正确的。

但缺点也很明显:数据绑定的是通过指令写在View的模版中的,所以调试比较复杂,需要对框架比较了解才能调试。

 

总结

MVC、MVP和MVVM模式之间并不是升级关系,各自有自己的使用场景MVVM更适合页面框架层面的搭建使用,特别是移动端APP的开发,目前热门的React Native和Weex都是采用MVVM模式开发;MVP适合复杂模板数据控制使用;MVC则适合小业务功能的实现。

更多的使用场景需要我们不断的总结,新的模式也需要在实践中创造。

转载于:https://my.oschina.net/nandy007/blog/819209

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值