⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 9932.txt

📁 关于编程技术技巧的文章
💻 TXT
📖 第 1 页 / 共 5 页
字号:
事件的元素标记中都可以将它作为参数传递给脚本。在这一点上,清单1
和清单2中的实现是完全相同的(应该指出的是,在IE中将事件作为参数
传递并非完全必要,因为所有事件句柄都可以通过查询window.event对
象来确定哪一个事件调用了它)。以相同的方法传递事件参数是这两个
脚本的共同之处。 

   第三,请注意事件在多个句柄之间的传递方式。在IE下,除非有特
别指定(即设置事件的cancelBubble属性),否则事件就不断向更高一
级传递。也就是说,如果文档内为同一事件指定了多个不同处理句柄,
IE的缺省行为是不断在多个句柄之间传递该事件,直到所有句柄都有机
会执行为止。而在Navigator下则大不相同,事件在不同级别的句柄之间
持续传播需要显式调用routeEvent。此外,在Navigator下调用handleEvent
还可以将事件直接(不是按照一般的分级次序)传递给任意元素。而在
IE下,则可以调用元素的事件方法模仿handleEvent,如: 

document.myElement.onmousedown() 


   最后应该注意的是,两个脚本以完全相反的次序处理事件:对运行
于Navigator的代码来说,click事件先在文档这一级被捕获,然后才是
按钮;而运行于IE的脚本则恰恰相反。 

   相对而言,Navigator的事件处理方法略显繁琐。当程序调用
captureEvents、系统接受请求之后,脚本就拥有对该事件的完全控制权
了。如果要把事件传递给其它句柄,就必须显式地调用routeEvent。如
上例,bodyEvent捕获鼠标单击事件之后就一直保留对它的控制权,直到
后来调用routeEvent才把事件“释放给”buttonEvent。 

   如果在IE中创建了一个文档级的事件句柄,可以理解为在文档级创
建了一个事件处理器,它能够处理所有“上传”到这一级的特定类别事件。
只要触发事件的元素不处理它,或虽经初始处理仍允许上传(缺省),事
件就可能被其它的句柄处理。在上例中,单击按钮使得buttonEvent被执
行。在buttonEvent结束之后,系统就自动在更高一级查找是否存在要求
处理该事件的句柄。在文档这一级它找到了符合要求的句柄,于是控制权
就转到bodyEvent。当bodyEvent结束执行,再次开始寻找处理句柄(此时
在窗口一级),但寻找失败,于是结束整个事件处理过程。 

   通过以上分析可以看出,要求同一脚本在不同浏览器下行为相同,
最大的问题在于两者事件传递次序和实现方法上的不同。由此,我们的解
决方法是:放弃两个浏览器的事件传递模型和事件分级概念,用一个通用
的事件接口取代之。

二、建立统一的事件控制接口 

   如清单3所示,Events.js实现了一个通用的事件接口。该接口并不
直接在元素与事件之间建立关联,所有的事件均在文档这一级捕获,直到
句柄被调用时才考虑事件的原始来源。而事件的类型(如click或mouseover)
以及触发该事件的元素(如上例中的myButton)可以通过分析事件对象来
获知。这里不再存在“事件级别”这一概念,这是因为对于任意一个指定
事件来说,句柄被调用的次序不再反映元素包含与被包含这一文档中的概
念。事件句柄的调用次序决定于其注册次序。 

【清单3 events.js】 
// 创建"事件-句柄"列表 
// 该列表中登记所有需要捕获的事件 
// 而每一个事件又分别对应于一组事件处理句柄 
var eventHandler = new eventHandlerArray( null ); 

// 当前运行环境 
var fNetscape = navigator.appName.indexOf( "Microsoft" ) !=-1? false : true; 

// 创建公用的事件对象 
var xEvent = new xEventObject(); 

function xEventObject() 
{ 
this.event = null; 
this.element = null; 
return this; 
} 

function eventHandlerArray() 
{ 
return new Array( eventHandlerItem( null ) ); 
} 

function eventHandlerItem( handlerID ) 
{ 
// 事件处理句柄数组 
this.funcList = new Array() 
// 事件标识ID(事件名称) 
this.hID = handlerID 
return this 
} 

// 在"事件-句柄"列表中登记一组新的事件-句柄 
function hookEvent() 
{ 
var theEvent = hookEvent.arguments[0] 
var theHook = hookEvent.arguments[1] 

// 在文档级捕获指定事件 
if ( fNetscape == true ) 
{ 
// Navigator: 指定在文档级捕获的事件及其句柄runEvent 
if ( document.captureEvents ) 
{ 
eval( "document.captureEvents( Event." + theEvent.toUpperCase() + " );" ); 
eval( "document.on" + theEvent + " = runEvent;" ); 
} 
else return; 
} 
else 
{ 
// IE: 指定在文档级捕获的事件及其句柄runEvent 
if ( !eval( "document.on" + theEvent ) ) 
eval( "document.on" + theEvent + " = runEvent;" ); 
} 
// 当前事件是否已经登记? 
for ( i = 0; eventHandler[ i ]; i ++ ) 
{ 
if ( eventHandler[ i ].hID == theEvent ) 
{ 
// 当前事件已经登记,为该事件登记新的处理句柄 
for ( j = 0; eventHandler[ i ].funcList[ j ] != null; j ++ ) {} 
eventHandler[ i ].funcList[ j ] = theHook; 
return; 
} 
} 
// 当前事件不在事件-句柄列表中 
eventHandler[ i ] = new eventHandlerItem( null ); 
eventHandler[ i ].hID = theEvent 
eventHandler[ i ].funcList[0] = theHook 
} 

// 公用事件句柄,将捕获的事件转发给所有对应的用户句柄 
function runEvent( event ) 
{ 
if ( fNetscape == false ) 
{ 
xEvent.event = window.event; 
xEvent.element = window.event.srcElement; 
} 
else 
{ 
xEvent.event = event; 
xEvent.element = event.target; 
} 
// 在事件-句柄列表中查找指定事件 
for ( i = 0; eventHandler[ i ]; i ++ ) 
{ 
if ( eventHandler[ i ].hID == xEvent.event.type ) 
{ 
// 依次运行对应于该事件的各个句柄 
for ( j = 0; eventHandler[ i ].funcList[ j ]; j ++ ) 
{ 
retCode = eval( eventHandler[ i ].funcList[ j ] + "( xEvent )" ) 
if ( retCode == false ) 
{ 
event.cancelBubble = true; 
return false; 
} 
} 
// 当前事件的用户句柄运行结束,允许缺省处理 
return true; 
} 
} 
return false 
} 


   由前面的讨论过程可知,IE和Navigator在传递事件对象作为脚本参
数、事件对象属性、元素属性这几方面存在共同之处。为实现用统一的方
法使用其共同部分,在Events.js中声明了一个xEvent对象。xEvent包含两
个组成部分——event和element,前者对应于IE的window.event和Navigator
的event;后者对应于IE的window.event.srcElement和Navigator的event.target。
两者共同的事件属性包括type和cancelBubble,元素属性则包括type和name等。 

   全局变量eventHandler是一个动态分配的eventHandlerItem数组,其
中每一项包含事件名字(即事件ID,如click,mouseover)以及与该事件对
应的句柄数组。每当hookEvent被调用,程序就搜索eventHandler,检查指
定事件是否已经登记。如果是,则新的句柄被加入到已有的句柄数组末尾;
否则,就在eventHandler中创建新的eventHandlerItem项,然后在其中注册
当前事件及对应的事件处理句柄。 

   所有需要处理事件的脚本均在文档级捕获事件,且将它的事件处理句
柄指定为runEvent。runEvent是一个公用事件句柄,它负责将事件转发给真
正完成处理任务的用户句柄。一旦某个事件在文档级被捕获,则所有属于它
的用户句柄都被依次调用,从而完成事件处理任务。使用该事件接口的基本
过程可以为: 

   ⑴ 在HTML文档的开始部分包含events.js脚本。 

   ⑵ 在用户句柄文件中,调用hookEvent注册所有要求处理的事件,传
递给它的参数是事件名字以及处理该事件的句柄。 

   ⑶ 实现用户句柄。句柄返回真值表示允许其它脚本继续处理同一事件;
否则禁止进一步处理。 

   抛弃分级概念获得的好处在于:可以自由地在事件、句柄和触发事件
的元素之间建立关系。现在,整个HTML文档可以看成一个事件源,而该接口
则成为一个公用的事件转发器,要求处理某个事件的脚本只需进行适当的注
册并提供处理句柄即可。此外,基于该事件模型的HTML文档不再需要声明
(和实现)基于事件的功能,而是由一组分离的脚本文件实现。触发事件的
元素不再指定任何处理句柄,而是由脚本捕获这些事件及决定处理方法。由
此,采用统一的事件接口也有利于改善代码的结构化和可读性。

三、应用示例 

   清单4中的hFlipButton.js捕获mouseover、mouseout、click三个事件。
click事件被传递给flipState。对于NAME域包含单词“flip”的元素,
flipState切换其标题为“SLOW”或“FAST”。mouseover事件被传递给
highlightState函数。对于NAME域包含“highlight”的元素,highlightState
设置一个每秒触发三次的时钟,将元素标题的首尾字符改为闪烁的星号。
mouseout事件传递给unhighlightState,用来关闭元素标题的突出显示。此
脚本说明了应用通用事件接口的一般过程,以及在句柄中如何确定事件的原
始来源。 

【清单4 hFlipButton.js】 
< !-- 声明 FlipButton 要求捕获的事件及其处理句柄 --> 
hookEvent( "click", "flipState" ) 
hookEvent( "mouseover", "highlightState" ) 
hookEvent( "mouseout", "unhighlightState" ) 

// 存储正被突出显示的元素 
_hiElement = null; 
_timer = null; 

// 鼠标单击: 切换标题 
function flipState( e ) 
{ 
if ( e.element.name ) 
{ 
if ( e.element.name.toLowerCase().indexOf( "flip" ) != -1 ) 
{ 
var str = e.element.value; 
if ( str.indexOf( "SLOW" ) != -1 ) 
e.element.value = str.charAt( 0 ) == "*" ? "*FAST*" : " FAST "; 
else 
e.element.value = str.charAt( 0 ) == "*" ? "*SLOW*" : " SLOW "; 
} 
} 

return true; 
} 

// 鼠标进入: 标题突出显示(首尾加闪烁的星号) 
function highlightState( e ) 
{ 
if ( e.element.name ) 
{ 
if ( e.element.name.toLowerCase().indexOf( "highlight" ) != -1 ) 
{ 
// 设置当前元素为突出显示 
_hiElement = e.element; 
// 终止以前的时钟 
if ( _timer ) 
clearTimeout( _timer ); 
// 启动突出显示过程 
doHighlight(); 
} 
} 
return true; 
} 

// 鼠标离开: 恢复标题为非突出显示 
function unhighlightState( e ) 
{ 
if ( e.element.name ) 
{ 
if ( e.element.name.toLowerCase().indexOf( "highlight" ) != -1 ) 
{ 
if ( _hiElement != null ) 
{ 
str = _hiElement.value; 
// 如标题首尾位置含星号,则删除之 
if ( str.charAt( 0 ) == "*" ) 
{ 
_hiElement.value = " " + str.substring( 1, str.length - 1 ) + " "; 
} 
} 
_hiElement = null; 
} 
} 
return true; 
} 

function doHighlight() 
{ 
if ( _hiElement != null ) 
{ 
var str = _hiElement.value; 
// 将首尾字符在" " 和 "*" 之间切换 
if ( str.charAt( 0 ) == "*" ) 
_hiElement.value = " " + str.substring( 1, str.length - 1 ) + " "; 
else 
_hiElement.value = "*" + str.substring( 1, str.length - 1 ) + "*" 
// 设置切换操作的间隔时间 
_timer = setTimeout( "doHighlight()", 300 ); 
} 
} 


   清单5的hStatusWrite.js捕获同样三个事件。但这里mouseover事件由
statusWrite函数处理。statusWrite显示鼠标所经过元素的NAME域,或NAME
属性不存在时显示“未知元素”。mouseout事件由statusCancel处理。
statusCancel在鼠标离开某个元素时清除状态条信息。click事件由changeName
函数处理。对于所有在NAME域包含“mutable”字符串的元素,changeName
响应其click事件,赋予NAME域某个随机值,如“mutable flip”、“mutable 
highlight”、“mutable flip highlight”。在完成上述操作之后,
changeName显式地调用第二个句柄,使得对NAME域的改变可以立即反映出来。
此脚本说明通用事件接口动态维护事件句柄的能力。 

【清单5 hStatusWrite.js】 
< !-- 声明 StatusWrite 要求捕获的事件及其处理句柄 --> 
hookEvent( "mouseover", "statusWrite" ) 
hookEvent( "mouseout", "statusCancel" ) 
hookEvent( "click", "changeName" ) 

// 鼠标进入事件: 显示元素名字 
function statusWrite( e ) 
{ 
if ( e.element.name ) 
{ 
window.status = "元素: " + e.element.name 
} 
else 
{ 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -