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

本篇文章参考书籍《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

扫描二维码关注公众号,回复: 13049122 查看本文章

  访问者模式的应用不仅如此, 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

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

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

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



猜你喜欢

转载自blog.csdn.net/EcbJS/article/details/109814904