代码要写成别人看不懂的样子(十二)

本文介绍了JavaScript中的两种设计模式——访问者模式和中介者模式。访问者模式允许在不改变对象结构的情况下,为对象结构中的元素定义新的操作方法,例如在处理IE事件绑定时通过call方法改变作用域。中介者模式则是通过中介对象封装对象间的交互,降低耦合度,避免对象直接引用。文中通过示例代码详细解释了这两种模式的实现和应用场景。

本篇文章参考书籍《JavaScript设计模式》–张容铭

前言

  大家接触到 web 之后,一定会遇到一个硬骨头— IE 浏览器。当年我的一位学长告诉过我, IE 不死,社会将无法进步,那时年少无知,不理解其中的含义,现在时过境迁,回头想想,路人心在看,却是局中人~。
在这里插入图片描述
  跑题了跑题了,聊回我们今天的主题,大家肯定都会遇到过,当写好一部分功能之后,调试好了,有时可能已经部署到服务器上了,这时候项目经理拍了拍你的肩膀,和蔼可亲的对你说,来了个小需求,改动不大,你给加一下。

  这个时候,你要仔细斟酌一下这个“ 改动不大 ”的意思,一般来说,是指的结果改动不大,那具体实现过程改动大不大,就内部消化一下吧。

  为了减少上面那种情况,我们今天一起研究个新设计模式,访问者模式。

访问者模式

   针对对象结构中的元素,定义在不改变对象的前提下访问结构中元素的新方法

  举个例子,在 IE 中,用 attachEvent 绑定事件的时候, this 指向的是 window 而不是当前这个元素,那我们在使用 this 的时候,就会报错:

var bindEvent = function(dom, type, fn) {
	if(dom.addEventListener) {
		dom.addEventListener(type, fn, false);
	} else if(dom.attachEvent) {
		dom.attachEvent('on' + type, fn);
	} else{
		dom['on' + type] = fn;
	}
}
var demo = document.getElementById('demo');
bindEvent(demo. 'click', function() {
	this.style.background = 'red'; //IE中会报this.style为空或不为对象
});

  现在修改方法有两种,一种是把所有的 this 替换成 demo ,这种方法显然会修改方法内部参数,后续修改维护可能成本更大,那还有一种方法就是,给操作元素增加新的操作方法,如下:

function bindIEEvent(dom, type, fn, data) {
	var data = data || {};
	dom.attachEvent('on' + type, function(e) {
		fn.call(dom, e, data);
	});
};

  核心方法是调用了一次 call 方法,我们知道 call apply 是更改函数执行时的作用域,这正是访问者模式的精髓,通过这个方法,我们就可以让某个对象在其他作用域中运行。

  还有一个有点是, call apply 允许我们添加额外的参数,这一点很重要,因为有时候只通过事件参数 e 获取到的数据不够。

function $(id) {return document.getElementById(id)};
bindIEEvent($('btn'), 'click', function(e, d) {
	$('test').innerHTML = e.type + d.text + this.tagName;
}, {text: 'test demo'});

  上面代码,点击一下 id btn 的按钮, id test 的段落内容会变为 click test demo BUTTON

  访问者模式的应用不仅如此, JS 原生对象构造器就设计成一个访问者,比如在判断数据类型的时候,我们通过 Object.prototype.toString.call 的方式。这里我们可以扩展一下,在我们为对象添加属性的时候通常都是没有顺序的,这样就很难找到最后一次添加的属性,如果能像处理数组一样处理对象,就好了,我们通过 push 添加数据,通过 pop 删除最后一个成员。

  这里我们需要创建一个访问器,将必要的方法封装在里面,方便使用。

//访问器
var Vistor = (function() {
	return {
		//截取方法
		splice: function() {
			//splice 方法参数,从原参数的第二个参数开始算起
			var args = Array.prototype.splice.call(arguments, 1);
			//对第一个参数对象执行 splice 方法
			return Array.prototype.splice.apply(arguments[0], args);
		},
		//追加数据方法
		push: function() {
			//强化数组对象,使它拥有 length 属性
			var len = arguments[0].length || 0;
			//添加的数据从原参数的第二个参数算起
			var args = this.splice(arguments, 1);
			//校正 length 属性
			arguments[0].length = len + arguments.length - 1;
			//对第一个参数对象执行 push 方法
			return Array.prototype.push.apply(arguments[0], args);
		},
		//弹出最后一次添加的元素
		pop: function() {
			//对第一个参数对象执行 pop 方法
			return Array.prototype.pop.apply(arguments[0]);
		}
	}
})();

  有了这个访问器,我们就可以操作数组对象了。

var a = new Object();
console.log(a.length);   //undefined
Visitor.push(a, 1,2,3,4);
console.log(a.length);  //4
Visitor.push(a, 4,5,6);
console.log(a);         //Object {0: 1, 1: 2, 2: 3, 3: 4, 4: 4, 5: 5, 6: 6, lenght: 7}
Visitor.splice(a, 2);
console.log(a);         //Obkect {0: 1, 1: 2, length: 2}

  访问者模式可以解决数据与数据操作方法之间的耦合,将数据的操作方法独立于数据,使其可以自由演变,因此访问者模式适用于那些数据稳定,但是操作方法易变的化境。

中介者模式

   通过中介对象封装一些列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合 。

  中介模式与我们之前提到的观察者模式有点类似,它们都是通过消息的收发机制实现的,有点不同的是,观察者模式中,一个对象既可以是消息的发送者,也可以是接收者,他们之间交流信息依托于消息系统实现的解耦。

  而中介者模式中,消息发送只能通过中介对象来完成,对象不能订阅消息,只有那些活跃对象(订阅者),才可订阅中介者的消息,而且观察者模式还需要写一个消息系统,那样会增加开发成本。下面我们写一个中介者对象:

//中介者对象
var Mediator = function() {
	//消息对象
	var _msg = {};
	return {
		/**
		* 订阅消息方法
		* 参数 type    消息名称
		* 参数 action  消息回调函数
		*/
		register: function(type, action) {
			//如果该消息存在
			if(_msg[type]) {
				//存入回调函数
				msg[type].push(action);
			} else {
				//不存在则建立消息容器
				_msg[type] = [];
				//存入新消息回调函数
				_msg[type].push(action);
			}
		},
		/**
		* 发布消息方法
		* 参数 type    消息名称
		*/
		send: function(type) {
			//如果该消息已经被订阅
			if(_msg[type]) {
				//遍历已经存储的消息回调函数
				for(var i = 0, len = _msg[type].length; i < len; i++) {
					//执行该回调函数
					_msg[type][i] && _msg[type][i]();
				}
			}
		}
	}
} ();

  中介者创建出来了,为了保险起见,先用测试用例试验一下。

//单元测试
//订阅demo消息,执行回调函数--输出 first
Mediator.register('demo', function() {
	console.log('first');
})
//订阅demo消息,执行回调函数--输出 second
Mediator.register('demo', function() {
	console.log('second');
})
//发布 demo 消息
Mediator.send('demo');
//输出结果以此为
//first
//second

  熟悉外观模式的同学可能觉得这个和外观模式也有点像,中介者模式会对多个对象交互的封装,且这些对象一般处于同一层面上,并且封装的交互在中介者内部,而外观模式的目的是为了提供更简单易用的接口。

  本节的两个设计模式还是比较易懂的,大家使用设计模式的时候,可能会越用越乱,感觉这个和之前接触的别的设计模式有点像,但是又忘记具体是哪一个,总是有种狗熊掰棒子的感觉,就会现在这一个。

  其实这种感觉和正常,知识本来就有相似性,感觉乱说明你懂得多,这个时候就是一个量变转化质变的过程,各位同学坚持住,多看几遍,多用几遍,下一遍就能醍醐灌顶。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值