ADS4.3 响应用户操作和事件——注册事件

前端技术 everyinch 4004℃ 0评论

浏览器不仅具有不同的事件和事件流处理方式,而且它们注册事件侦听器的方式也不一样。

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()方法会先于图像载入完成触发,而第一个和最后一个都需要一直等到最后才会触发。如下图:
blog_addLoadEvent

分享&收藏

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

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
'; } if( dopt('d_footcode_b') ) echo dopt('d_footcode'); ?>