第18章 高级技巧 (三)

 

18.3 自定义事件

在本书前面,你已经学到事件是 JavaScript 与浏览器交互的主要途径。事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。对象可以发布事件,用来表示在该对象生命周期中某个有趣的时候到了。然后其他对象可以观察该对象,等待这些有趣的时刻到来并通过运行代码来响应。

观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正常运作即使观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数 (事件处理程序)。涉及 DOM 上时,DOM 元素便是主体,你的事件处理代码便是观察者。

事件是与 DOM 交互的最常见的方式,但它们也可以用于非 DOM 代码中 -- 通过实现自定义事件。自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式可以如下定义:

function EventTarget(){
	this.handlers = {};	
}
EventTarget.prototype = {
	constructor: EventTarget,
	addHandler: function(type, handler){
		if (typeof this.handlers[type] == "undefined"){
			this.handlers[type] = [];
		}
		this.handlers[type].push(handler);
	},
	fire: function(event){
		if (!event.target){
			event.target = this;	
		}
		if (this.handlers[event.type] instanceof Array){
			var handlers = this.handlers[event.type];
			for (var i=0, len=handlers.length; i<len; i++){
				handlers[i](event);
			}
		}
	},
	removeHandler: function(type, handler){
		if(this.handlers[type] instanceof Array){
			var handlers = this.handlers[type];
			for(var i=0, len=handlers.length; i<len; i++){
				if(handlers[i] === handler){
					break;
				}
			}
			handlers.splice(i, 1);
		}
	}	
};

EventTarget 类型有一个单独的属性 handlers,用于存储事件处理程序。还有三个方法:addHandler(),用于注册给定类型事件的事件处理程序;fire(),用于触发一个事件;以及removeHandler(),用于注销某个事件类型的事件处理程序。

addHandler() 方法接受两个参数:事件类型和用于处理该事件的函数。当调用该方法时,会进行一次检查,看看 handlers 属性中是否已经存在一个针对该事件类型的数组;如果没有,则创建一个新的。然后使用 push() 将该处理程序添加到数组的末尾。

如果要触发一个事件,要调用 fire() 函数。该方法接受一个单独的参数,是一个至少包含 type 属性的对象。fire() 方法先给 event 对象设置一个 target 属性,如果它尚未被指定的话。然后它就查找对应该事件类型的一组处理程序,调用各个函数,并给出 event 对象。因为这些都是自定义事件,所以 event 对象上还需要的额外信息由你自己决定。

removeHandler() 方法是 addHandler() 的辅助,它们接受的参数一样:事件的类型和事件处理程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。如果找到了,则使用 break 操作符退出 for 循环。然后使用 splice() 方法将该项目从数组中删除。

然后,使用 EventTarget 类型的自定义事件可以如下使用:

function handleMessage(event){
	alert("Message received: " + event.message);	
}
// 创建一个新对象
var target = new EventTarget();
// 添加一个事件处理程序
target.addHandler("message", handleMessage);
// 触发事件
target.fire({ type: "message", message: "Hello world!"});
// 删除事件处理程序
target.removeHandler("message", handleMessage);
// 再次,应没有处理程序
target.fire({ type: "message", message: "Hello world!"});

在这段代码中,定义了 handleMessage() 函数用于处理 message 事件。它接受 event 对象并输出 message 属性。调用 target 对象的 addHandler() 方法并传给 "message" 以及 handleMessage() 函数。在接下来的一行上,调用了 fire() 函数,并传递了包含2个属性,即 type 和 message 的对象直接量。它会调用 message 事件的事件处理程序,这样就会显示一个警告框 (来自 handleMessage())。然后删除了事件处理程序,这样即使事件再次触发,也不会显示任何警告框。

因为这种功能是封装在一种自定义类型中的,其他对象可以继承 EventTarget 并获得这个行为,如下例所示:

function Person(name, age){
	EventTarget.call(this);
	this.name = name;
	this.age = age;
}
inheritPrototype(Person, EventTarget);
Person.prototype.say = function(message){
	this.fire({type: "message", message: message});
};


Person 类型使用了寄生组合继承 (参见第6章) 方法来继承 EventTarget 。一旦调用了 say() 方法,便触发了事件,它包含了消息的细节。在某种类型的另外的方法中调用 fire() 方法是很常见的,同时它通常不是公开调用的。这段代码可以如下使用:

function object(o){   // 原型式继承
	function F(){}
	F.prototype = o;
	return new F();
}

function inheritPrototype(subType, superType){     // 寄生组合式继承
	var prototype = object(superType.prototype);   // 创建对象
	prototype.constructor = subType;               // 增强对象  
	subType.prototype = prototype;                 // 指定对象   
}

function EventTarget(){
	this.handlers = {};	
}

EventTarget.prototype = {
	constructor: EventTarget,
	addHandler: function(type, handler){
		if (typeof this.handlers[type] == "undefined"){
			this.handlers[type] = [];
		}
		this.handlers[type].push(handler);
	},
	fire: function(event){
		if (!event.target){
			event.target = this;	
		}
		if (this.handlers[event.type] instanceof Array){
			var handlers = this.handlers[event.type];
			for (var i=0, len=handlers.length; i<len; i++){
				handlers[i](event);
			}
		}
	},
	removeHandler: function(type, handler){
		if(this.handlers[type] instanceof Array){
			var handlers = this.handlers[type];
			for(var i=0, len=handlers.length; i<len; i++){
				if(handlers[i] === handler){
					break;
				}
			}
			handlers.splice(i, 1);
		}
	}	
};
// 开始继承
function Person(name, age){
	EventTarget.call(this);      // 通过借用构造函数来继承属性
	this.name = name;
	this.age = age;
}

inheritPrototype(Person, EventTarget);  // 通过原型链的混成形式来继承方法

Person.prototype.say = function(message){
	this.fire({type: "message", message: message});
};

function handleMessage(event){
	alert(event.target.name + " says: " + event.message);
}
// 创建新 person
var person = new Person("Nicholas", 29);
// 添加一个事件处理程序
person.addHandler("message", handleMessage);
// 在该对象上调用1个方法,它触发消息事件
person.say("Hi there.");


 

这个例子中的 handleMessage() 函数显示了某人的名字 (通过 event.target.name 获得) 的一个警告框和消息正文。当调用 say() 方法并传递一个消息时,就会触发 message 事件。接下来,它又会调用 handleMessage() 函数并显示警告框。

当代码中存在多个部分在特定时刻相互交互的情况下,自定义事件就非常有用了。这时,如果每个对象都有对其他所有对象引用,那么整个代码就会紧密耦合,同时维护也变得很困难,因为对某个对象的修改也会影响到其他对象。使用自定义事件有助于解耦相关对象,保持功能的隔绝。在很多情况中,触发事件的代码和监听事件的代码是完全分离的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值