浏览器不仅具有不同的事件和事件流处理方式,而且它们注册事件侦听器的方式也不一样。
1. 嵌入式注册模型
例如想下面代码演示的嵌入式的事件侦听器:
<a href="http://example.com" onclick="window.open(this.href); return false;">http://example.com</a>
在事件的概念刚刚出现时,嵌入式事件注册是唯一可用的方法。它需要在标记中为每个事件作为HTML属性进行硬编码,从而 需要为每个元素编写相同的代码,这不仅导致标记混乱,而且还会使文件尺寸变大。
2. 深入理解ADS.addEvent()方法
到目前为止一直使用自定义的ADS.addEvent()方法将事件侦听器添加到DOM元素上。这个方法的最早版本是由Scott Andrew LePera((http://www.scottandrew.com/weblog/articles/cbs-events)开发的,是一个将W3C和Microsoft不同事件模型组合到一个函数中的方法:
function addEvent(obj, evType, fn, useCapture){
if (obj.addEventListener){
obj.addEventListener(evType, fn, useCapture);
return true;
} else if (obj.attachEvent){
var r = obj.attachEvent("on"+evType, fn);
return r;
} else {
alert("Handler could not be attached");
}
}
这个addEvent()方法的实现有一些问题:
(1) 由于Microsoft事件模型不允许捕获,因此其中的useCapture参数显得有点模糊
(2) 侦听器环境下this关键字在IE和W3C中也是不同的
自定义库ADS中创建的addEvent()方法实在John Resig(http://ejohn.org/projects/flexible-javascript-events/)版本基础上修改完成的:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
}
else{
obj.addEventListener( type, fn, false );
}
}
(1) 这个版本去掉了useCapture参数
(2) 将DOM的默认方式设置为事件冒泡(与IE相似)
(3) 通过使用匿名函数使this关键字在Microsoft和W3C环境中保持一致,this引用的是将侦听器指派给的对象
3. 传统事件模型
使用传统方法注册事件侦听器时就是定义事件触发时执行的方法,然后将这个方法指定给对象的事件侦听器属性:
// 传统的事件注册方式
window.onload = function() {
var anchor = document.getElementById('example');
anchor.onclick = function(){
// 触发单击事件时执行的代码
}
}
传统方法是最直观的也是不针对特定浏览器的事件注册方法,但这种方法也并不完美:
(1) 传统事件注册方法中的this关键字引用的是目标对象
(2) 事件侦听器只能是一个单独的函数。当然如果想调用多个侦听器,就必须将多个侦听器包装在一个函数体内
(3) 传统方法从属于浏览器默认的事件流,没有办法指定是在捕获阶段还是在冒泡阶段
4. Microsoft特色的事件模型
在Microsoft的解决方案中,分别使用attachEvent()和detachEvent()方法注册和移除事件侦听器。要在具体的对象上注册事件侦听器,必须首先定义一个函数,然后使用该对象的attachEvent(event,listener)方法注册事件。而事件的名称仍然使用与传统方法相同的on前缀:
// Microsoft事件注册
function eventListener() {
// 响应事件的代码
}
window.attachEvent('onload', function() {
var link = document.getElementById('example');
link.attachEvent('onclick',eventListener);
});
此后可以使用相同参数的detachEvent()方法移除事件侦听器:
link.detachEvent('onclick',eventListener);
使用Microsoft的方法,也可以为同一个对象指定多个事件侦听器:
link.attachEvent('onclick', eventListenerA);
link.attachEvent('onclick', eventListenerB);
link.attachEvent('onclick', eventListenerC);
而且,还可以使用fireEvent()方法手工调用事件:
link.fireEvent("onclick");
虽然Microsoft的方法比传统方法更清晰,但也有一些问题:
(1) 只对IE有效
(2) 与传统的模型不同,Microsoft的模型只是引用而非复制事件侦听器,因此在使用this关键字时,this引用的将是JavaScript函数,而不是注册事件侦听器的那个对象
(3) IE不支持捕获。除非只有cancelBubble属性来阻止冒泡,否则所有事件始终会冒泡
5. W3C DOM2事件模型
DOM2级事件规范中包含addEventListener()和removeEventListener()方法,这两个方法接受事件和事件侦听器参数,同时还允许通过第3个参数指定事件阶段。W3C去掉了on前缀的方案,因此所有事件必须使用事件名称而非传统的方法名称来标识:
// W3C事件注册
function eventListener() {
// 响应单击事件的代码
}
window.addEventListener('load', function(W3CEvent) {
var link = document.getElementById('example');
link.addEventListener('click',eventListener,false);
}, false);
第3个参数如果是true,事件侦听器将在捕获阶段内执行;如果是false,则会在冒泡阶段触发。
可以使用removeEventListener()方法移除事件侦听器:
link.removeEventListener('click',eventListener,false);
而且,还可以为同一个对象添加任意多个事件侦听器:
link.addEventListener('click',eventListenerA,false);
link.addEventListener('click',eventListenerB,false);
link.addEventListener('click',eventListenerC,false);
在W3C模型中,也可以通过组合document.createEvent()方法和对象的dispatchEvent()方法来手工调用事件。例如要模仿一次单击事件,需要创建一个MouseEvent事件,并且在分派给DOM元素之前初始化:
// 使用W3C的方法手工调用事件
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
'click', // 事件类型
true, // 可以冒泡
true, // 可以取消默认动作
window, // 视图类型
0, // 鼠标点击数
0, // 屏幕的x坐标
0, // 屏幕的y坐标
0, // 客户端的x坐标
0, // 客户端的y坐标
false, // 是否按下Ctrl键
false, // 是否按下Alt键
false, // 是否按下Shift键
false, // 是否按下Meta键
0, // 按下鼠标的次数
null // 相关的目标对象
);
anchor.dispatchEvent(evt);
6. load事件的问题
无论使用哪种事件注册方法,通过window对象的load事件来初始化DOM脚本都会存在一个固有的问题:当页面总包含许多大文件时,load事件会一直等到所有资源全部载入完毕后才会被触发。如果创建的应用程序需要使用嵌入的图像,那么可能会希望load事件在嵌入的图像载入完成之前运行。要解决这个问题,需要在自定义的ADS库中添加ADS.addLoadEvent()方法:
/**
* 注册load事件,在页面载入完成之后并在图像载入完成之前运行
*/
function addLoadEvent(loadEvent,waitForImages) {
if(!isCompatible()) return false;
// 如果等待标记是true,则使用常规的事件注册方法
if(waitForImages) {
return addEvent(window, 'load', loadEvent);
}
// 否则包装loadEvent()方法
// 为this关键字指定正确的内容
// 确保事件不会执行两次
var init = function() {
// 如果函数已经被调用了,则返回
if (arguments.callee.done) return;
// 标记函数已经运行
arguments.callee.done = true;
// 在document环境中运行载入事件
loadEvent.apply(document,arguments);
};
// 为DOMContentLoaded事件注册事件侦听器
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", init, false);
}
// 对于Safari使用setInterval()函数检测document是否载入完成
if (/WebKit/i.test(navigator.userAgent)) {
var _timer = setInterval(function() {
if (/loaded|complete/.test(document.readyState)) {
clearInterval(_timer);
init();
}
},10);
}
// 对于IE(使用条件注释)
// 附加一个最后执行的脚本,并检测该脚本是否载入完成
/*@cc_on @*/
/*@if (@_win32)
document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
if (this.readyState == "complete") {
init();
}
};
/*@end @*/
return true;
}
window['ADS']['addLoadEvent'] = addLoadEvent;
这个改进方法是在Dean Edwards()论述的解决方案为基础的,其中使用了不同的方法在图像完成载入之前调用DOM脚本:
□ 如果浏览器中存在addEventListener()方法,则使用DOMContentLoaded事件,该事件会在文档标记载入完成时调用
□ 对于Safari,则使用setInterval()函数周期性地检查document的readyState属性,随时监控文档是否载入完成
□ 对于IE,向文档中写入一个新的script标签,但该标签会延迟到文件最后载入。然后,使用script对象的onreadystatechange方法检查readyState属性
键ADS.addLoadEvent()方法的第2个参数设置为true,则可以调用包含在其它的原始的ADS.addEvent(window,’load’…)方法。
示例:加载一个580KB的JPG图像,使用的3个载入事件是:
// 使用常规的addEvent()方法为window对象注册load事件
ADS.addEvent(window,'load',function(W3CEvent) {
ADS.log.write('ADS.addEvent(window,load,...) invoked');
});
// 使用改进后的addLoadMethod()方法
ADS.addLoadEvent(function(W3CEvent) {
ADS.log.write('ADS.addLoadEvent(...) invoked');
});
// 通过改进后的addLoadMethod()方法调用原始的addEvent()方法
ADS.addLoadEvent(function(W3CEvent) {
ADS.log.write('ADS.addLoadEvent(...,true) invoked');
},true);
当加载页面后,中间的ADS.addLoadMethod()方法会先于图像载入完成触发,而第一个和最后一个都需要一直等到最后才会触发。如下图:

转载请注明:陈童的博客 » ADS4.3 响应用户操作和事件——注册事件